├── .gitignore ├── LICENSE ├── README.md ├── drakus ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py ├── img ├── 01.png ├── 02.png ├── 03.png ├── 04.png ├── 05.png ├── 06.png └── logo.png ├── logs └── log_files ├── manage.py ├── requirements.txt ├── static ├── css │ ├── material_icons.css │ ├── material_icons.ttf │ └── materialize.min.css ├── images │ └── logo.png └── js │ ├── jquery-3.5.1.min.js │ └── materialize.min.js ├── users ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── templates │ └── users │ │ └── login.html ├── tests.py ├── urls.py └── views.py └── watcher ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── libs ├── artifact_helpers.py ├── project_helpers.py ├── search_helpers.py ├── search_hybrid.py ├── search_otx.py ├── search_urlscan.py ├── search_vt.py └── url_helpers.py ├── models.py ├── templates └── watcher │ ├── artifact_list.html │ ├── base.html │ ├── base_project.html │ ├── create_artifact.html │ ├── create_project.html │ ├── create_url.html │ ├── edit_project.html │ ├── hash_info.html │ ├── project_list.html │ ├── search_form.html │ └── url_list.html ├── tests.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drakus - Artifact Monitoring Tool 2 | **Drakus** allows you to monitor the artifacts and domains used in a Red Team exercise to see if they have been uploaded to certain online malware analysis services. 3 | 4 | ![](img/logo.png) 5 | 6 | Artifacts are queried at (requires a free API key): 7 | - Virustotal: [https://www.virustotal.com](https://www.virustotal.com) 8 | - Hybrid Analysis: [https://www.hybrid-analysis.com](https://www.hybrid-analysis.com/) 9 | - AlienVault OTX: [https://otx.alienvault.com](https://otx.alienvault.com) 10 | 11 | The domains are queried at: 12 | - Urlscan.io: [https://urlscan.io](https://urlscan.io) 13 | 14 | Queries can be made manually and automatically every 120 minutes (it's possible to change the time in *drakus/setting.py*). 15 | 16 | ## Installation 17 | Install with virtualenv: 18 | ```bash 19 | python3 -m venv venv 20 | source venv/bin/activate 21 | pip3 install -r requirements.txt 22 | ``` 23 | 24 | Initialize the database: 25 | ``` 26 | python3 manage.py makemigrations watcher 27 | python manage.py migrate 28 | ``` 29 | Create an admin user (it's possible to create more users from the admin panel): 30 | ``` 31 | python manage.py createsuperuser 32 | ``` 33 | 34 | ## Running drakus 35 | Launch server: 36 | ```bash 37 | python manage.py runserver 38 | 39 | Performing system checks... 40 | 41 | System check identified no issues (0 silenced). 42 | December 01, 2020 - 22:49:35 43 | Django version 3.0.8, using settings 'drakus.settings' 44 | Starting development server at http://127.0.0.1:8000/ 45 | Quit the server with CONTROL-C. 46 | ``` 47 | 48 | ## Images 49 | - Login page: 50 | ![](img/01.png) 51 | - Create project form: 52 | ![](img/02.png) 53 | - Project list: 54 | ![](img/03.png) 55 | - Artifact list: 56 | ![](img/04.png) 57 | - Url list: 58 | ![](img/05.png) 59 | - Search form: 60 | ![](img/06.png) -------------------------------------------------------------------------------- /drakus/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/drakus/__init__.py -------------------------------------------------------------------------------- /drakus/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for drakus project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drakus.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /drakus/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for drakus project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.8. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'o=pa5-^a=!86)d#!)fg6t*068hk7*dvjme(b47rp7tp5)h@o68' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ['*'] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'watcher.apps.WatcherConfig', 35 | 'users.apps.UsersConfig', 36 | 'django.contrib.admin', 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'drakus.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'drakus.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # background tasks 120 | ARTIFACTS_TIMER = 120 121 | DOMAINS_TIMER = 120 122 | 123 | # Static files (CSS, JavaScript, Images) 124 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 125 | 126 | STATIC_URL = '/static/' 127 | 128 | STATICFILES_DIRS = [ 129 | os.path.join(BASE_DIR, "static"), 130 | ] 131 | 132 | LOGIN_REDIRECT_URL = '/users/login' 133 | LOGIN_URL = '/users/login' 134 | 135 | LOGGING = { 136 | 'version': 1, 137 | 'disable_existing_loggers': False, 138 | 'formatters': { 139 | 'verbose': { 140 | 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 141 | 'style': '{', 142 | }, 143 | 'simple': { 144 | 'format': '{levelname} {asctime} - {message}', 145 | 'style': '{', 146 | }, 147 | 'access': { 148 | 'format': '{asctime} - {message}', 149 | 'style': '{', 150 | }, 151 | }, 152 | 'handlers': { 153 | 'console': { 154 | 'level': 'ERROR', 155 | 'class': 'logging.StreamHandler', 156 | 'formatter': 'simple', 157 | }, 158 | 'accessInfo': { 159 | 'level': 'INFO', 160 | 'class': 'logging.FileHandler', 161 | 'filename': 'logs/app_access.log', 162 | 'formatter': 'access', 163 | }, 164 | 'fileInfo': { 165 | 'level': 'INFO', 166 | 'class': 'logging.FileHandler', 167 | 'filename': 'logs/app_info.log', 168 | 'formatter': 'simple', 169 | }, 170 | 'fileError': { 171 | 'level': 'ERROR', 172 | 'class': 'logging.FileHandler', 173 | 'filename': 'logs/app_error.log', 174 | 'formatter': 'simple', 175 | }, 176 | 177 | }, 178 | 'loggers': { 179 | 'django': { 180 | 'handlers': ['accessInfo'], 181 | 'level': 'INFO', 182 | 'propagate': True, 183 | }, 184 | 'drakus.info': { 185 | 'handlers': ['fileInfo', 'console'], 186 | 'level': 'INFO', 187 | 'propagate': True, 188 | }, 189 | 'drakus.error': { 190 | 'handlers': ['console', 'fileError'], 191 | 'level': 'ERROR', 192 | 'propagate': True, 193 | } 194 | }, 195 | } 196 | -------------------------------------------------------------------------------- /drakus/urls.py: -------------------------------------------------------------------------------- 1 | """drakus URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import include, path 18 | from watcher import views 19 | 20 | urlpatterns = [ 21 | path('', views.get_projects, name='projects'), 22 | path('watcher/', include('watcher.urls')), 23 | path('users/', include('users.urls')), 24 | path('admin/', admin.site.urls), 25 | ] 26 | -------------------------------------------------------------------------------- /drakus/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for drakus project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drakus.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /img/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/img/01.png -------------------------------------------------------------------------------- /img/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/img/02.png -------------------------------------------------------------------------------- /img/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/img/03.png -------------------------------------------------------------------------------- /img/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/img/04.png -------------------------------------------------------------------------------- /img/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/img/05.png -------------------------------------------------------------------------------- /img/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/img/06.png -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/img/logo.png -------------------------------------------------------------------------------- /logs/log_files: -------------------------------------------------------------------------------- 1 | logs folder 2 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drakus.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==3.0.8 2 | OTXv2==1.5.10 3 | APScheduler==3.6.3 4 | requests==2.24.0 -------------------------------------------------------------------------------- /static/css/material_icons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url(material_icons.ttf) format('truetype'); 6 | } 7 | 8 | .material-icons { 9 | font-family: 'Material Icons'; 10 | font-weight: normal; 11 | font-style: normal; 12 | font-size: 24px; 13 | line-height: 1; 14 | letter-spacing: normal; 15 | text-transform: none; 16 | display: inline-block; 17 | white-space: nowrap; 18 | word-wrap: normal; 19 | direction: ltr; 20 | } 21 | -------------------------------------------------------------------------------- /static/css/material_icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/static/css/material_icons.ttf -------------------------------------------------------------------------------- /static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/static/images/logo.png -------------------------------------------------------------------------------- /static/js/jquery-3.5.1.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 2 | 3 | 4 | Drakus 5 | {% load static %} 6 | 7 | 8 | 9 | 10 | 40 | 41 | 42 | 43 |
44 |
45 |
46 |

Drakus

47 | {% load static %} 48 | 49 | 50 |
51 | 52 |
Please, login into your account
53 |
54 | 55 |
56 |
57 | 58 |
59 | {{ form.as_p }} 60 | {% csrf_token %} 61 | 62 |
63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | app_name = 'users' 6 | urlpatterns = [ 7 | path('', views.login, name='login'), 8 | path('login', views.login, name='login'), 9 | path('logout', views.logout, name='logout'), 10 | ] 11 | -------------------------------------------------------------------------------- /users/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponseRedirect 2 | 3 | from django.shortcuts import render 4 | from django.contrib.auth import logout as do_logout 5 | from django.contrib.auth import login as do_login 6 | from django.contrib.auth import authenticate 7 | from django.contrib.auth.forms import AuthenticationForm 8 | 9 | # login functions 10 | def login(request): 11 | form = AuthenticationForm() 12 | if request.method == "POST": 13 | form = AuthenticationForm(data=request.POST) 14 | if form.is_valid(): 15 | username = form.cleaned_data['username'] 16 | password = form.cleaned_data['password'] 17 | user = authenticate(username=username, password=password) 18 | if user is not None: 19 | do_login(request, user) 20 | return HttpResponseRedirect('/watcher') 21 | 22 | return render(request, "users/login.html", {'form': form}) 23 | 24 | 25 | def logout(request): 26 | do_logout(request) 27 | return HttpResponseRedirect('/users/login') -------------------------------------------------------------------------------- /watcher/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aetsu/drakus/05e2b564985d10efcd8f9822a6b85c1681e7f752/watcher/__init__.py -------------------------------------------------------------------------------- /watcher/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Project, Artifact, Url 4 | 5 | 6 | class ArtifactInline(admin.StackedInline): 7 | model = Artifact 8 | extra = 1 9 | 10 | class UrlInline(admin.StackedInline): 11 | model = Url 12 | extra = 1 13 | 14 | class ProjectAdmin(admin.ModelAdmin): 15 | fieldsets = [ 16 | (None, {'fields': ['name']}), 17 | ('Creation Date', {'fields': ['creation_date']}), 18 | ('VT API key', {'fields': ['vt_api']}), 19 | ('Hybrid Analysis API key', {'fields': ['hybrid_api']}), 20 | ('OTX Alienvault',{'fields': ['otx_api']}), 21 | ] 22 | inlines = [ArtifactInline, UrlInline] 23 | list_display = ('name', 'creation_date') 24 | list_filter = ['creation_date'] 25 | search_fields = ['name'] 26 | 27 | 28 | admin.site.register(Project, ProjectAdmin) 29 | -------------------------------------------------------------------------------- /watcher/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class WatcherConfig(AppConfig): 5 | name = 'watcher' 6 | 7 | def ready(self): 8 | from .libs.search_helpers import updater 9 | updater() 10 | -------------------------------------------------------------------------------- /watcher/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | from django import forms 3 | from .models import Project, Artifact, Url 4 | 5 | class SearchHashForm(forms.Form): 6 | hash_query = forms.CharField( 7 | label='Hash', max_length=300, widget=forms.TextInput(attrs={'class': 'validate'})) 8 | 9 | 10 | class SearchUrlForm(forms.Form): 11 | url_query = forms.CharField( 12 | label='Url', max_length=300, widget=forms.TextInput(attrs={'class': 'validate'})) 13 | 14 | 15 | class CreateArtifactForm(ModelForm): 16 | class Meta: 17 | model = Artifact 18 | fields = ['name', 'hash_query'] 19 | 20 | 21 | class CreateUrlForm(ModelForm): 22 | class Meta: 23 | model = Url 24 | fields = ['name', 'url_query'] 25 | 26 | 27 | class CreateProjectForm(ModelForm): 28 | class Meta: 29 | model = Project 30 | fields = ['name', 'vt_api', 'hybrid_api', 'otx_api'] 31 | 32 | class EditProjectForm(ModelForm): 33 | class Meta: 34 | model = Project 35 | fields = ['id', 'name', 'vt_api', 'hybrid_api', 'otx_api'] 36 | -------------------------------------------------------------------------------- /watcher/libs/artifact_helpers.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from django.utils.timezone import make_aware 3 | 4 | from ..models import Artifact 5 | from .project_helpers import get_project_info_helper 6 | import logging 7 | 8 | log_info = logging.getLogger('drakus.info') 9 | log_error = logging.getLogger('drakus.error') 10 | 11 | def create_artifact_helper(artifact_data): 12 | try: 13 | c_project_obj = get_project_info_helper(artifact_data['c_project']) 14 | a = Artifact(name=artifact_data['name'], hash_query=artifact_data['hash_query'], 15 | project=c_project_obj) 16 | a.save() 17 | except Exception as e: 18 | log_error.error("create_artifact_helper <" + str(e) + '>') 19 | 20 | 21 | def get_artifacts_helper(c_project): 22 | artifact_l = [] 23 | try: 24 | artifact_l = Artifact.objects.filter(project=c_project).all() 25 | except Exception as e: 26 | log_error.error("get_artifacts_helper <" + str(e) + '>') 27 | return artifact_l 28 | 29 | 30 | def update_artifact_date_helper(object_id): 31 | artifact_obj = Artifact.objects.get(pk=object_id) 32 | artifact_obj.last_check = make_aware(datetime.now()) 33 | artifact_obj.save() 34 | 35 | 36 | def delete_artifact_helper(object_id): 37 | try: 38 | artifact_obj = Artifact.objects.get(pk=object_id) 39 | artifact_obj.delete() 40 | except Exception as e: 41 | log_error.error("delete_artifact_helper <" + str(e) + '>') 42 | -------------------------------------------------------------------------------- /watcher/libs/project_helpers.py: -------------------------------------------------------------------------------- 1 | from ..models import Project, Artifact, Url 2 | from django.db.models import Q 3 | import logging 4 | 5 | log_info = logging.getLogger('drakus.info') 6 | log_error = logging.getLogger('drakus.error') 7 | 8 | def create_project_helper(project_data): 9 | try: 10 | p = Project(name=project_data['name'], vt_api=project_data['vt_api'], 11 | hybrid_api=project_data['hybrid_api'], otx_api=project_data['otx_api']) 12 | p.save() 13 | except Exception as e: 14 | log_error.error('create_project_helper <' + str(e) + '>') 15 | 16 | 17 | def delete_project_helper(project_id): 18 | try: 19 | p = Project.objects.get(pk=project_id) 20 | p.delete() 21 | except Exception as e: 22 | log_error.error("delete_project_helper <" + str(e) + '>') 23 | 24 | 25 | def get_project_info_helper(project_id): 26 | r = None 27 | try: 28 | p = Project.objects.get(pk=project_id) 29 | except Exception as e: 30 | log_error.error('get_project_info_helper <' + str(e) + '>') 31 | return p 32 | 33 | 34 | def get_projects_helper(): 35 | project_q = Project.objects.all() 36 | project_l = [] 37 | for p in project_q: 38 | d_aux = {} 39 | d_aux['id'] = p.id 40 | d_aux['name'] = p.name 41 | d_aux['creation_date'] = p.creation_date 42 | d_aux['artifacts'] = Artifact.objects.filter(project=p.id).count() 43 | artifact_l = Artifact.objects.filter( 44 | Q(project=p.id), (Q(vt_exists=1) | Q(hybrid_exists=1) | Q(otx_exists=1))) 45 | d_aux['identified_artifacts'] = len(artifact_l) 46 | d_aux['urls'] = Url.objects.filter(project=p.id).count() 47 | url_l = Url.objects.filter( 48 | Q(project=p.id), (Q(urlscan_exists=1))) 49 | d_aux['identified_urls'] = len(url_l) 50 | project_l.append(d_aux) 51 | return project_l 52 | -------------------------------------------------------------------------------- /watcher/libs/search_helpers.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from django.conf import settings 3 | 4 | from ..models import Artifact, Url, Config 5 | from ..libs.search_vt import search_vt_hash, update_artifact_vt 6 | from ..libs.search_hybrid import search_hybdrid_hash, update_artifact_hybrid 7 | from ..libs.search_otx import search_otx_hash, update_artifact_otx 8 | from ..libs.search_urlscan import search_urlscan_url, update_url_urlscan 9 | from ..libs.project_helpers import get_project_info_helper 10 | from ..libs.artifact_helpers import update_artifact_date_helper 11 | from ..libs.url_helpers import update_url_date_helper 12 | 13 | from datetime import datetime 14 | from apscheduler.schedulers.background import BackgroundScheduler 15 | import logging 16 | 17 | log_info = logging.getLogger('drakus.info') 18 | log_error = logging.getLogger('drakus.error') 19 | 20 | 21 | def updater(): 22 | # artifacts_time = Config.objects.get('') 23 | scheduler = BackgroundScheduler() 24 | scheduler.add_job(search_enabled_artifacts_helper, 'interval', minutes=settings.ARTIFACTS_TIMER) 25 | scheduler.add_job(search_enabled_url_helper, 'interval', minutes=settings.DOMAINS_TIMER) 26 | scheduler.start() 27 | 28 | 29 | def search_artifact_helper(hash_query, c_project_id): 30 | d_response = { 31 | 'artefact_vt_info': {}, 32 | 'artefact_hybrid_info': {}, 33 | 'artefact_otx_info': {} 34 | } 35 | c_project = get_project_info_helper(c_project_id) 36 | try: 37 | vt_response = search_vt_hash(hash_query, c_project.vt_api) 38 | if not 'ERROR' in vt_response: 39 | d_response['artefact_vt_info'] = vt_response 40 | log_info.info('search_vt_hash: <' + hash_query + ': ' + str(vt_response['positives']) + '/' + str(vt_response['total']) + '>') 41 | except Exception as e: 42 | log_error.error('search_artifact_helper VT <' + str(e) + '>') 43 | try: 44 | hybrid_response = search_hybdrid_hash(hash_query, c_project.hybrid_api) 45 | if not 'ERROR' in hybrid_response: 46 | d_response['artefact_hybrid_info'] = hybrid_response 47 | log_info.info('search_hybrid_hash: <' + hash_query + ': ' + str(hybrid_response['av_detect']) + '% (' + str(hybrid_response['verdict']) + ')>') 48 | except Exception as e: 49 | log_error.error('search_artifact_helper Hybrid <' + str(e) + '>') 50 | try: 51 | otx_response = search_otx_hash(hash_query, c_project.otx_api) 52 | if not 'ERROR' in otx_response: 53 | d_response['artefact_otx_info'] = otx_response 54 | if otx_response['otx_link'] != '-': 55 | log_info.info('search_otx_hash: <' + hash_query + ': 1>') 56 | else: 57 | log_info.info('search_otx_hash: <' + hash_query + ': 0>') 58 | except Exception as e: 59 | log_error.error('search_artifact_helper OTX <' + str(e) + '>') 60 | return d_response 61 | 62 | 63 | def search_project_artifact_helper(artifact_id): 64 | artifact_obj = Artifact.objects.get(id=artifact_id) 65 | update_artifact_date_helper(artifact_id) 66 | c_project = artifact_obj.project 67 | try: 68 | vt_response = search_vt_hash(artifact_obj.hash_query, c_project.vt_api) 69 | if not 'ERROR' in vt_response: 70 | log_info.info('VT:' + artifact_obj.hash_query + 71 | ': ' + str(vt_response['response_code'])) 72 | update_artifact_vt(artifact_id, vt_response) 73 | else: 74 | log_error.error('search_project_artifact_helper <' + 75 | vt_response['ERROR'] + '>') 76 | except Exception as e: 77 | log_error.error( 78 | 'search_project_artifact_helper ') 79 | 80 | try: 81 | hybrid_response = search_hybdrid_hash( 82 | artifact_obj.hash_query, c_project.hybrid_api) 83 | if not 'ERROR' in hybrid_response: 84 | log_info.info('Hybrid:' + artifact_obj.hash_query + 85 | ': ' + str(hybrid_response['hybrid_exists'])) 86 | update_artifact_hybrid(artifact_id, hybrid_response) 87 | else: 88 | log_error.error('search_project_artifact_helper <' + 89 | hybrid_response['ERROR'] + '>') 90 | except Exception as e: 91 | log_error.error( 92 | 'search_project_artifact_helper ') 93 | 94 | try: 95 | otx_response = search_otx_hash( 96 | artifact_obj.hash_query, c_project.otx_api) 97 | if not 'ERROR' in otx_response: 98 | log_info.info('Otx:' + artifact_obj.hash_query + 99 | ': ' + str(otx_response['otx_exists'])) 100 | update_artifact_otx(artifact_id, otx_response) 101 | else: 102 | log_error.error('search_project_artifact_helper <' + 103 | otx_response['ERROR'] + '>') 104 | except Exception as e: 105 | log_error.error( 106 | 'search_project_artifact_helper ') 107 | 108 | 109 | def search_enabled_artifacts_helper(project_id=None): 110 | if project_id is None: 111 | artifact_l = Artifact.objects.filter(enabled=1).all() 112 | else: 113 | artifact_l = Artifact.objects.filter( 114 | enabled=1).filter(project=project_id).all() 115 | for artifact_obj in artifact_l: 116 | update_artifact_date_helper(artifact_obj.id) 117 | artifact_project = artifact_obj.project 118 | try: 119 | vt_response = search_vt_hash( 120 | artifact_obj.hash_query, artifact_project.vt_api) 121 | if not 'ERROR' in vt_response: 122 | log_info.info('VT:' + artifact_obj.hash_query + 123 | ': ' + str(vt_response['response_code'])) 124 | update_artifact_vt(artifact_obj.id, vt_response) 125 | else: 126 | log_error.error( 127 | 'search_enabled_artifacts_helper <' + vt_response['ERROR'] + '>') 128 | except Exception as e: 129 | log_error.error( 130 | 'search_enabled_artifacts_helper ') 131 | try: 132 | hybrid_response = search_hybdrid_hash( 133 | artifact_obj.hash_query, artifact_project.hybrid_api) 134 | if not 'ERROR' in hybrid_response: 135 | log_info.info('Hybrid:' + artifact_obj.hash_query + 136 | ': ' + str(hybrid_response['hybrid_exists'])) 137 | update_artifact_hybrid(artifact_obj.id, hybrid_response) 138 | else: 139 | log_error.error( 140 | 'search_enabled_artifacts_helper <' + hybrid_response['ERROR'] + '>') 141 | except Exception as e: 142 | log_error.error( 143 | 'search_enabled_artifacts_helper ') 144 | try: 145 | otx_response = search_otx_hash( 146 | artifact_obj.hash_query, artifact_project.otx_api) 147 | if not 'ERROR' in otx_response: 148 | log_info.info('Otx:' + artifact_obj.hash_query + 149 | ': ' + str(otx_response['otx_exists'])) 150 | update_artifact_otx(artifact_obj.id, otx_response) 151 | else: 152 | log_error.error( 153 | 'search_enabled_artifacts_helper <' + otx_response['ERROR'] + '>') 154 | except Exception as e: 155 | log_error.error( 156 | 'search_enabled_artifacts_helper ') 157 | 158 | 159 | def search_project_url_helper(url_id): 160 | url_obj = Url.objects.get(id=url_id) 161 | update_url_date_helper(url_id) 162 | c_project = url_obj.project 163 | # try: 164 | # otx_response = search_otx_url( 165 | # url_obj.url_query, c_project.otx_api) 166 | # if not 'ERROR' in otx_response: 167 | # print('Otx url:' + url_obj.url_query + 168 | # ': ' + str(otx_response['otx_exists'])) 169 | # update_url_otx(url_id, otx_response) 170 | # else: 171 | # print(otx_response['ERROR']) 172 | # except Exception as e: 173 | # print('Error: OTX search/update DB' + str(e)) 174 | try: 175 | urlscan_response = search_urlscan_url( 176 | url_obj.url_query) 177 | if not 'ERROR' in urlscan_response: 178 | log_info.info('Urlscan.io url:' + url_obj.url_query + 179 | ': ' + str(urlscan_response['urlscan_exists'])) 180 | update_url_urlscan(url_id, urlscan_response) 181 | else: 182 | log_error.error('search_project_url_helper <' + 183 | urlscan_response['ERROR'] + '>') 184 | except Exception as e: 185 | log_error.error( 186 | 'search_project_url_helper ') 187 | 188 | 189 | def search_enabled_url_helper(project_id=None): 190 | if project_id is None: 191 | url_l = Url.objects.filter(enabled=1).all() 192 | else: 193 | url_l = Url.objects.filter(enabled=1).filter(project=project_id).all() 194 | for url_obj in url_l: 195 | url_obj = Url.objects.get(id=url_obj.id) 196 | update_url_date_helper(url_obj.id) 197 | c_project = url_obj.project 198 | # try: 199 | # otx_response = search_otx_url( 200 | # url_obj.url_query, c_project.otx_api) 201 | # if not 'ERROR' in otx_response: 202 | # print('Otx url:' + url_obj.url_query + 203 | # ': ' + str(otx_response['otx_exists'])) 204 | # update_url_otx(url_obj.id, otx_response) 205 | # else: 206 | # print(otx_response['ERROR']) 207 | # except Exception as e: 208 | # print('Error: OTX search/update DB' + str(e)) 209 | try: 210 | urlscan_response = search_urlscan_url( 211 | url_obj.url_query) 212 | if not 'ERROR' in urlscan_response: 213 | log_info.info('Urlscan.io url:' + url_obj.url_query + 214 | ': ' + str(urlscan_response['urlscan_exists'])) 215 | update_url_urlscan(url_obj.id, urlscan_response) 216 | else: 217 | log_error.error('search_enabled_url_helper <' + 218 | urlscan_response['ERROR'] + '>') 219 | except Exception as e: 220 | log_error.error( 221 | 'search_enabled_url_helper ') 222 | -------------------------------------------------------------------------------- /watcher/libs/search_hybrid.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from ..models import Artifact 3 | import logging 4 | 5 | log_info = logging.getLogger('drakus.info') 6 | log_error = logging.getLogger('drakus.error') 7 | 8 | 9 | def search_hybdrid_hash(query, api_key): 10 | d_res = {} 11 | url = 'https://www.hybrid-analysis.com/api/v2/search/hash' 12 | payload = {'hash': query} 13 | headers = { 14 | 'accept': 'application/json', 15 | 'user-agent': 'Falcon Sandbox', 16 | 'Content-Type': 'application/x-www-form-urlencoded', 17 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', 18 | 'api-key': api_key 19 | } 20 | try: 21 | r = requests.post(url, data=payload, headers=headers) 22 | if r.status_code == 200: 23 | hybrid_data = r.json() 24 | l_tags = ['verdict', 'analysis_start_time', 25 | 'threat_score', 'av_detect', 'md5', 'sha256', 'sha1'] 26 | if len(hybrid_data) == 0: 27 | d_res['hybrid_exists'] = 0 28 | d_res['hybrid_link'] = '-' 29 | else: 30 | hybrid_data = hybrid_data[0] 31 | d_res['hybrid_exists'] = 1 32 | d_res['hybrid_link'] = 'https://www.hybrid-analysis.com/sample/' + \ 33 | hybrid_data['sha256'] 34 | for tag in l_tags: 35 | if tag in hybrid_data: 36 | d_res[tag] = hybrid_data[tag] 37 | else: 38 | d_res[tag] = '-' 39 | else: 40 | log_error.error('search_hybdrid_hash ') 41 | except Exception as e: 42 | d_res['ERROR'] = 'Hybrid query: ' + str(e) 43 | log_error.error('search_hybdrid_hash ') 44 | return d_res 45 | 46 | 47 | def update_artifact_hybrid(object_id, hybrid_data): 48 | artifact_obj = Artifact.objects.get(pk=object_id) 49 | artifact_obj.hybrid_exists = hybrid_data['hybrid_exists'] 50 | artifact_obj.hybrid_link = hybrid_data['hybrid_link'] 51 | artifact_obj.hash_md5 = hybrid_data['md5'] 52 | artifact_obj.hash_sha1 = hybrid_data['sha1'] 53 | artifact_obj.hash_sha256 = hybrid_data['sha256'] 54 | artifact_obj.save() 55 | -------------------------------------------------------------------------------- /watcher/libs/search_otx.py: -------------------------------------------------------------------------------- 1 | from OTXv2 import OTXv2 2 | import IndicatorTypes 3 | from ..models import Artifact, Url 4 | import logging 5 | 6 | log_info = logging.getLogger('drakus.info') 7 | log_error = logging.getLogger('drakus.error') 8 | 9 | def search_otx_hash(query, api_key): 10 | d_res = { 11 | 'otx_exists': 0, 12 | 'otx_link': '-', 13 | 'analysis_start_time': '-', 14 | 'md5': '-', 15 | 'sha1': '-', 16 | 'sha256': '-' 17 | } 18 | l_tags = ['md5', 'sha1', 'sha256'] 19 | otx_analysis = None 20 | otx = OTXv2(api_key) 21 | try: 22 | if len(query) == 32: 23 | otx_res = otx.get_indicator_details_full( 24 | IndicatorTypes.FILE_HASH_MD5, query) 25 | elif len(query) == 40: 26 | otx_res = otx.get_indicator_details_full( 27 | IndicatorTypes.FILE_HASH_SHA1, query) 28 | elif len(query) == 64: 29 | otx_res = otx.get_indicator_details_full( 30 | IndicatorTypes.FILE_HASH_SHA256, query) 31 | else: 32 | d_res['ERROR'] = 'OTX query: INVALID HASH' 33 | log_error.error('search_otx_hash ') 34 | return d_res 35 | if 'analysis' in otx_res: 36 | if 'analysis' in otx_res['analysis']: 37 | otx_analysis = otx_res['analysis']['analysis'] 38 | if otx_analysis is not None: 39 | d_res['otx_exists'] = 1 40 | if 'datetime_int' in otx_analysis: 41 | d_res['analysis_start_time'] = otx_analysis['datetime_int'] 42 | d_res['otx_link'] = 'https://otx.alienvault.com/indicator/file/' + query 43 | if 'info' in otx_analysis and 'results' in otx_analysis['info']: 44 | otx_results = otx_analysis['info']['results'] 45 | for tag in l_tags: 46 | if tag in otx_results: 47 | d_res[tag] = otx_results[tag] 48 | else: 49 | d_res[tag] = '-' 50 | except Exception as e: 51 | d_res['ERROR'] = 'OTX artifact query: ' + str(e) 52 | log_error.error('search_otx_hash ') 53 | return d_res 54 | 55 | 56 | def update_artifact_otx(object_id, otx_data): 57 | artifact_obj = Artifact.objects.get(pk=object_id) 58 | artifact_obj.otx_exists = otx_data['otx_exists'] 59 | artifact_obj.otx_link = otx_data['otx_link'] 60 | artifact_obj.hash_md5 = otx_data['md5'] 61 | artifact_obj.hash_sha1 = otx_data['sha1'] 62 | artifact_obj.hash_sha256 = otx_data['sha256'] 63 | artifact_obj.save() 64 | 65 | 66 | # def search_otx_url(query, api_key): 67 | # d_res = { 68 | # 'otx_exists': 0, 69 | # 'otx_link': 'https://otx.alienvault.com/indicator/url/' + query, 70 | # 'analysis_start_time': '-', 71 | # } 72 | # otx_analysis = None 73 | # otx = OTXv2(api_key) 74 | # try: 75 | # otx_res = otx.get_indicator_details_full(IndicatorTypes.URL, query) 76 | # pulse_info = otx_res['general']['pulse_info'] 77 | # d_res['otx_exists'] = int(pulse_info['count']) 78 | # url_list = otx_res['url_list']['url_list'] 79 | # d_res['analysis_start_time'] = url_list[0]['date'] 80 | # except Exception as e: 81 | # d_res['ERROR'] = 'OTX url query: ' + str(e) 82 | # return d_res 83 | 84 | # def update_url_otx(object_id, otx_data): 85 | # url_obj = Url.objects.get(pk=object_id) 86 | # url_obj.otx_exists = otx_data['otx_exists'] 87 | # url_obj.otx_link = otx_data['otx_link'] 88 | # url_obj.save() 89 | -------------------------------------------------------------------------------- /watcher/libs/search_urlscan.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from ..models import Url 3 | import logging 4 | 5 | log_info = logging.getLogger('drakus.info') 6 | log_error = logging.getLogger('drakus.error') 7 | 8 | def search_urlscan_url(query): 9 | query = query.replace('https://','').replace('http://','') 10 | d_res = { 11 | 'urlscan_exists':0, 12 | 'urlscan_link':'https://urlscan.io/result/', 13 | 'first_search': '-', 14 | } 15 | url = 'https://urlscan.io/api/v1/search/' 16 | payload = {'q': 'domain:' + query} 17 | headers = { 18 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' 19 | } 20 | try: 21 | r = requests.get(url, params=payload, headers=headers) 22 | if r.status_code == 200: 23 | urlscan_data = r.json() 24 | if len(urlscan_data['results']) > 0: 25 | urlscan_data = urlscan_data['results'][0] 26 | d_res['urlscan_exists'] = 1 27 | d_res['first_search'] = urlscan_data['task']['time'] 28 | d_res['urlscan_link'] += urlscan_data['_id'] 29 | except Exception as e: 30 | d_res['ERROR'] = 'Urlscan.io query: ' + str(e) 31 | log_error.error('search_urlscan_url ') 32 | return d_res 33 | 34 | def update_url_urlscan(object_id, urlscan_data): 35 | url_obj = Url.objects.get(pk=object_id) 36 | url_obj.urlscan_exists = urlscan_data['urlscan_exists'] 37 | url_obj.urlscan_link = urlscan_data['urlscan_link'] 38 | url_obj.save() -------------------------------------------------------------------------------- /watcher/libs/search_vt.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from ..models import Artifact 3 | import logging 4 | 5 | log_info = logging.getLogger('drakus.info') 6 | log_error = logging.getLogger('drakus.error') 7 | 8 | 9 | def search_vt_hash(hash, api_key): 10 | url = 'https://www.virustotal.com/vtapi/v2/file/report' 11 | params = {'apikey': api_key, 'resource': hash} 12 | response = requests.get(url, params=params) 13 | d_res = {} 14 | if response.status_code == 200: 15 | vt_data = response.json() 16 | l_tags = ['response_code', 'scan_date', 'permalink', 17 | 'total', 'positives', 'md5', 'sha256', 'sha1'] 18 | for tag in l_tags: 19 | if tag in vt_data: 20 | d_res[tag] = vt_data[tag] 21 | else: 22 | d_res[tag] = '-' 23 | else: 24 | d_res['ERROR'] = 'VT Status code: ' + str(response.status_code) 25 | log_error.error('search_vt_hash ') 26 | return d_res 27 | 28 | 29 | def update_artifact_vt(object_id, vt_data): 30 | artifact_obj = Artifact.objects.get(pk=object_id) 31 | artifact_obj.vt_exists = vt_data['response_code'] 32 | artifact_obj.vt_link = vt_data['permalink'] 33 | artifact_obj.hash_md5 = vt_data['md5'] 34 | artifact_obj.hash_sha1 = vt_data['sha1'] 35 | artifact_obj.hash_sha256 = vt_data['sha256'] 36 | artifact_obj.save() 37 | -------------------------------------------------------------------------------- /watcher/libs/url_helpers.py: -------------------------------------------------------------------------------- 1 | from django.utils.timezone import make_aware 2 | from datetime import datetime 3 | 4 | from ..models import Url 5 | from .project_helpers import get_project_info_helper 6 | import logging 7 | 8 | log_info = logging.getLogger('drakus.info') 9 | log_error = logging.getLogger('drakus.error') 10 | 11 | 12 | def create_url_helper(url_data): 13 | try: 14 | c_project_obj = get_project_info_helper(url_data['c_project']) 15 | a = Url(name=url_data['name'], url_query=url_data['url_query'], 16 | project=c_project_obj) 17 | a.save() 18 | except Exception as e: 19 | log_error.error("create_url_helper <" + str(e) + '>') 20 | 21 | 22 | def get_urls_helper(c_project): 23 | url_l = [] 24 | try: 25 | url_l = Url.objects.filter(project=c_project).all() 26 | except Exception as e: 27 | log_error.error("get_urls_helper <" + str(e) +'>') 28 | return url_l 29 | 30 | 31 | def update_url_date_helper(object_id): 32 | url_obj = Url.objects.get(pk=object_id) 33 | url_obj.last_check = make_aware(datetime.now()) 34 | url_obj.save() 35 | 36 | 37 | def delete_url_helper(object_id): 38 | try: 39 | url_obj = Url.objects.get(pk=object_id) 40 | url_obj.delete() 41 | except Exception as e: 42 | log_error.error("delete_url_helper <" + str(e) +'>') 43 | -------------------------------------------------------------------------------- /watcher/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | 4 | 5 | class Project(models.Model): 6 | name = models.CharField(max_length=100) 7 | creation_date = models.DateTimeField(default=timezone.now) 8 | vt_api = models.CharField(max_length=200, null=True) 9 | hybrid_api = models.CharField(max_length=200, null=True) 10 | otx_api = models.CharField(max_length=200, null=True) 11 | 12 | def __str__(self): 13 | return self.name 14 | 15 | 16 | class Artifact(models.Model): 17 | project = models.ForeignKey(Project, on_delete=models.CASCADE) 18 | name = models.CharField(max_length=100) 19 | hash_query = models.CharField(max_length=64, null=True) 20 | hash_md5 = models.CharField(max_length=32, blank=True, null=True) 21 | hash_sha1 = models.CharField(max_length=40, blank=True, null=True) 22 | hash_sha256 = models.CharField(max_length=64, blank=True, null=True) 23 | vt_exists = models.IntegerField(blank=True, default=0) 24 | vt_link = models.CharField(max_length=300, blank=True, null=True) 25 | hybrid_exists = models.IntegerField(blank=True, default=0) 26 | hybrid_link = models.CharField(max_length=300, blank=True, null=True) 27 | otx_exists = models.IntegerField(blank=True, default=0) 28 | otx_link = models.CharField(max_length=300, blank=True, null=True) 29 | last_check = models.DateTimeField(default=timezone.now) 30 | enabled = models.IntegerField(blank=True, default=1) 31 | 32 | def __str__(self): 33 | return self.name 34 | 35 | class Url(models.Model): 36 | project = models.ForeignKey(Project, on_delete=models.CASCADE) 37 | name = models.CharField(max_length=100) 38 | url_query = models.CharField(max_length=300, null=True) 39 | urlscan_exists = models.IntegerField(blank=True, default=0) 40 | urlscan_link = models.CharField(max_length=300, blank=True, null=True) 41 | # otx_exists = models.IntegerField(blank=True, default=0) 42 | # otx_link = models.CharField(max_length=300, blank=True, null=True) 43 | last_check = models.DateTimeField(default=timezone.now) 44 | enabled = models.IntegerField(blank=True, default=1) 45 | 46 | def __str__(self): 47 | return self.name 48 | 49 | class Config(models.Model): 50 | name = models.CharField(max_length=100) 51 | value = models.IntegerField(default=180) 52 | 53 | def __str__(self): 54 | return self.name -------------------------------------------------------------------------------- /watcher/templates/watcher/artifact_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'watcher/base_project.html' %} {% block content %} 2 |
3 |
4 |
5 |
6 |
7 | + 9 |
10 |
11 | searchALL 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% for artifact in artifact_l %} 31 | 32 | 33 | 34 | {% if artifact.vt_exists == 1%} 35 | 37 | {% else %} 38 | 39 | {% endif %} {% if artifact.hybrid_exists == 1%} 40 | 42 | {% else %} 43 | 44 | {% endif %} {% if artifact.otx_exists == 1%} 45 | 47 | {% else %} 48 | 49 | {% endif %} 50 | 51 | 52 | 56 | 65 | 66 | {% endfor %} 67 | 68 |
NameQueryVirusTotalHybrid AnalysisAlienVault OTXLast check
{{artifact.name}}{{artifact.hash_query}}errorerrorerror{{artifact.last_check}} 53 | 54 | delete 55 | 57 |
58 | {% csrf_token %} 59 | 60 | 63 |
64 |
69 |
70 | 75 | {% endblock %} -------------------------------------------------------------------------------- /watcher/templates/watcher/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Drakus 5 | {% load static %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 24 | {% block content %} {% endblock %} 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /watcher/templates/watcher/base_project.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Drakus 5 | {% load static %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 28 | {% block content %} {% endblock %} 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /watcher/templates/watcher/create_artifact.html: -------------------------------------------------------------------------------- 1 | {% extends 'watcher/base_project.html' %} {% block content %} 2 |
3 |

Create artifact

4 |
5 |
6 | {% csrf_token %} 7 |
8 |
9 | {{ createArtifact.name }} 10 | 11 |
12 |
13 |
14 |
15 | {{ createArtifact.hash_query }} 16 | 17 |
18 |
19 |
20 | 23 |
24 |
25 |
26 |
27 |
28 | 30 |
31 |
32 | {% endblock %} -------------------------------------------------------------------------------- /watcher/templates/watcher/create_project.html: -------------------------------------------------------------------------------- 1 | {% extends 'watcher/base_project.html' %} {% block content %} 2 |
3 |

Create project

4 |
5 |
6 | {% csrf_token %} 7 |
8 |
9 |
10 | {{ createProjectForm.name }} 11 | 12 |
13 |
14 |
15 |
16 | {{ createProjectForm.vt_api }} 17 | 18 |
19 |
20 |
21 |
22 | {{ createProjectForm.hybrid_api }} 23 | 24 |
25 |
26 |
27 |
28 | {{ createProjectForm.otx_api }} 29 | 30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 | 42 |
43 |
44 | {% endblock %} -------------------------------------------------------------------------------- /watcher/templates/watcher/create_url.html: -------------------------------------------------------------------------------- 1 | {% extends 'watcher/base_project.html' %} {% block content %} 2 |
3 |

Create url

4 |
5 |
6 | {% csrf_token %} 7 |
8 |
9 | {{ createUrl.name }} 10 | 11 |
12 |
13 |
14 |
15 | {{ createUrl.url_query }} 16 | 17 |
18 |
19 |
20 | 23 |
24 |
25 |
26 |
27 |
28 | 30 |
31 |
32 | {% endblock %} -------------------------------------------------------------------------------- /watcher/templates/watcher/edit_project.html: -------------------------------------------------------------------------------- 1 | {% extends 'watcher/base_project.html' %} {% block content %} 2 |
3 |

Edit project

4 |
5 |
6 | {% csrf_token %} 7 |
8 |
9 |
10 | {{ editProjectForm.name }} 11 | 12 |
13 |
14 |
15 |
16 | {{ editProjectForm.vt_api }} 17 | 18 |
19 |
20 |
21 |
22 | {{ editProjectForm.hybrid_api }} 23 | 24 |
25 |
26 |
27 |
28 | {{ editProjectForm.otx_api }} 29 | 30 |
31 |
32 |
33 |
36 |
37 |
38 | 39 |
40 |
41 | 43 |
44 |
45 | {% endblock %} -------------------------------------------------------------------------------- /watcher/templates/watcher/hash_info.html: -------------------------------------------------------------------------------- 1 | {% extends 'watcher/base_project.html' %} {% block content %} 2 |
3 |
4 |
5 |

Query:

6 |
7 |
8 |

{{ hash_query }}

9 |
10 |
11 |
12 |

VirusToltal

13 | {% if artefact_vt_info.response_code == 1%} 14 |
15 |
16 |
Md5:
17 |
{{ artefact_vt_info.md5 }}
18 |
19 |
20 |
Sha1:
21 |
{{ artefact_vt_info.sha1 }}
22 |
23 |
24 |
Sha256:
25 |
{{ artefact_vt_info.sha256 }}
26 |
27 |
28 |
Scan date:
29 |
{{ artefact_vt_info.scan_date }}
30 |
31 |
32 |
Results:
33 |
{{ artefact_vt_info.positives }}/{{ artefact_vt_info.total }}
34 |
35 |
36 |
Link:
37 | 38 |
39 | {% else %} 40 |

No results found

41 | {% endif %} 42 |
43 |
44 |

Hybrid Analysis

45 | {% if artefact_hybrid_info.hybrid_exists == 1%} 46 |
47 |
48 |
Md5:
49 |
{{ artefact_hybrid_info.md5 }}
50 |
51 |
52 |
Sha1:
53 |
{{ artefact_hybrid_info.sha1 }}
54 |
55 |
56 |
Sha256:
57 |
{{ artefact_hybrid_info.sha256 }}
58 |
59 |
60 |
Scan date:
61 |
{{ artefact_hybrid_info.analysis_start_time }}
62 |
63 |
64 |
Results:
65 |
{{ artefact_hybrid_info.av_detect }} % ({{ artefact_hybrid_info.verdict }})
66 |
67 | 71 | {% else %} 72 |

No results found

73 | {% endif %} 74 |
75 |
76 |

AlienVault OTX

77 | {% if artefact_otx_info.otx_exists == 1%} 78 |
79 |
80 |
Md5:
81 |
{{ artefact_otx_info.md5 }}
82 |
83 |
84 |
Sha1:
85 |
{{ artefact_otx_info.sha1 }}
86 |
87 |
88 |
Sha256:
89 |
{{ artefact_otx_info.sha256 }}
90 |
91 |
92 |
Scan date:
93 |
{{ artefact_otx_info.analysis_start_time }}
94 |
95 |
96 |
Link:
97 | 98 |
99 | {% else %} 100 |

No results found

101 | {% endif %} 102 |
103 |
104 | {% endblock %} -------------------------------------------------------------------------------- /watcher/templates/watcher/project_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'watcher/base.html' %} 2 | {% block content %} 3 |
4 |
5 |
6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% for project in project_l %} 25 | 26 | 27 | 28 | 29 | {% if project.identified_artifacts > 0%} 30 | 31 | {% else %} 32 | 33 | {% endif %} 34 | 35 | {% if project.identified_urls > 0%} 36 | 37 | {% else %} 38 | 39 | {% endif %} 40 | 44 | 48 | 57 | 58 | {% endfor %} 59 | 60 |
NameCreation dateArtifactsIdentified ArtifactsUrlsIdentified url
{{project.name}}{{project.creation_date}}{{project.artifacts}}{{project.identified_artifacts}}{{project.identified_artifacts}}{{project.urls}}{{project.identified_urls}}{{project.identified_urls}} 41 | 42 | mode_edit 43 | 45 | 46 | delete 47 | 49 |
50 | {% csrf_token %} 51 | 52 | 55 |
56 |
61 |
62 | {% endblock %} 63 | -------------------------------------------------------------------------------- /watcher/templates/watcher/search_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'watcher/base_project.html' %} {% block content %} 2 |
3 |

Search hash

4 |
5 | {% csrf_token %} 6 |
7 | {{ hash_form.hash_query }} 8 | 9 |
10 | 13 |
14 | 26 |
27 | 28 | {% endblock %} -------------------------------------------------------------------------------- /watcher/templates/watcher/url_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'watcher/base_project.html' %} {% block content %} 2 |
3 |
4 |
5 |
6 | + 8 |
9 |
10 | searchALL 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% for url in url_l %} 27 | 28 | 29 | 30 | {% if url.urlscan_exists == 1%} 31 | 33 | {% else %} 34 | 35 | {% endif %} 36 | 37 | 41 | 50 | 51 | {% endfor %} 52 | 53 |
URLQueryurlscan.ioLast check
{{url.name}}{{url.url_query}}error{{url.last_check}} 38 | 39 | delete 40 | 42 |
43 | {% csrf_token %} 44 | 45 | 48 |
49 |
54 |
55 | 60 | {% endblock %} -------------------------------------------------------------------------------- /watcher/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /watcher/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | app_name = 'watcher' 6 | urlpatterns = [ 7 | path('', views.get_projects, name='projects'), 8 | path('projects', views.get_projects, name='projects'), 9 | path('create_project', views.create_project, name='create_project'), 10 | path('edit_project/', views.edit_project, name='edit_project'), 11 | path('delete_project/', views.delete_project, name='delete_project'), 12 | path('set_project', views.set_project, name='set_project'), 13 | path('artifacts', views.get_artifacts, name='artifacts'), 14 | path('delete_artifact/', views.delete_artifact, name='delete_artifact'), 15 | path('create_artifact', views.create_artifact, name='create_artifact'), 16 | path('search_artifact', views.search_artifact, name='search_artifact'), 17 | path('search_all_artifacts', views.search_all_artifacts, name='search_all_artifacts'), 18 | path('urls', views.get_urls, name='urls'), 19 | path('delete_url/', views.delete_url, name='delete_url'), 20 | path('create_url', views.create_url, name='create_url'), 21 | path('search_url', views.search_url, name='search_url'), 22 | path('search_all_urls', views.search_all_urls, name='search_all_urls'), 23 | path('search', views.search, name='search'), 24 | path('search_hash', views.search_hash, name='search_hash'), 25 | ] 26 | -------------------------------------------------------------------------------- /watcher/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse, HttpResponseRedirect 2 | from django.shortcuts import render 3 | from django.contrib.auth.decorators import login_required 4 | 5 | from .forms import SearchHashForm, SearchUrlForm, CreateArtifactForm, CreateProjectForm, EditProjectForm, CreateUrlForm 6 | from .libs.project_helpers import create_project_helper, get_projects_helper, get_project_info_helper, delete_project_helper 7 | from .libs.search_helpers import search_artifact_helper, search_project_artifact_helper, search_project_url_helper, search_enabled_artifacts_helper, search_enabled_url_helper 8 | from .libs.artifact_helpers import create_artifact_helper, get_artifacts_helper, delete_artifact_helper 9 | from .libs.url_helpers import get_urls_helper, create_url_helper, delete_url_helper 10 | import logging 11 | 12 | log_info = logging.getLogger('drakus.info') 13 | log_error = logging.getLogger('drakus.error') 14 | 15 | # projects 16 | @login_required 17 | def create_project(request, *args, **kwargs): 18 | if request.method == 'POST': 19 | form = CreateProjectForm(request.POST) 20 | if form.is_valid(): 21 | project_data = { 22 | 'name': form.cleaned_data['name'], 23 | 'vt_api': form.cleaned_data['vt_api'], 24 | 'hybrid_api': form.cleaned_data['hybrid_api'], 25 | 'otx_api': form.cleaned_data['otx_api'] 26 | } 27 | create_project_helper(project_data) 28 | return HttpResponseRedirect('/watcher/projects') 29 | else: 30 | createProjectForm = CreateProjectForm() 31 | return render(request, 'watcher/create_project.html', {'createProjectForm': createProjectForm}) 32 | 33 | @login_required 34 | def edit_project(request, project_id): 35 | p = get_project_info_helper(project_id) 36 | if request.method == 'POST': 37 | form = EditProjectForm(request.POST, instance=p) 38 | if form.is_valid(): 39 | form.save() 40 | return HttpResponseRedirect('/watcher/projects') 41 | else: 42 | # p = get_project_info_helper(project_id) 43 | editProjectForm = EditProjectForm(instance=p) 44 | return render(request, 'watcher/edit_project.html', {'editProjectForm': editProjectForm}) 45 | 46 | @login_required 47 | def delete_project(request, *args, **kwargs): 48 | if request.method == 'GET': 49 | p_id = kwargs['project_id'] 50 | delete_project_helper(p_id) 51 | return HttpResponseRedirect('/watcher/projects') 52 | 53 | @login_required 54 | def get_projects(request): 55 | project_l = get_projects_helper() 56 | context = {'project_l': project_l} 57 | return render(request, 'watcher/project_list.html', context) 58 | 59 | @login_required 60 | def set_project(request, *args, **kwargs): 61 | if request.method == 'POST': 62 | project_id = request.POST.get('project_id', None) 63 | response = HttpResponseRedirect('/watcher/artifacts') 64 | response.set_cookie('current_project', int(project_id)) 65 | return response 66 | return HttpResponseRedirect('/watcher/projects') 67 | 68 | 69 | # artifacts 70 | @login_required 71 | def get_artifacts(request): 72 | if not 'current_project' in request.COOKIES: 73 | c_project = 0 74 | else: 75 | c_project = request.COOKIES['current_project'] 76 | try: 77 | artifact_l = get_artifacts_helper(c_project) 78 | context = {'artifact_l': artifact_l} 79 | return render(request, 'watcher/artifact_list.html', context) 80 | except Exception as e: 81 | print(e) 82 | return HttpResponseRedirect('/watcher/') 83 | 84 | @login_required 85 | def create_artifact(request, *args, **kwargs): 86 | if not 'current_project' in request.COOKIES or request.COOKIES['current_project'] == 0: 87 | return HttpResponseRedirect('/watcher/') 88 | if request.method == 'POST': 89 | form = CreateArtifactForm(request.POST) 90 | if form.is_valid(): 91 | artifact_data = { 92 | 'name': form.cleaned_data['name'], 93 | 'hash_query': form.cleaned_data['hash_query'], 94 | 'c_project': request.COOKIES['current_project'] 95 | } 96 | create_artifact_helper(artifact_data) 97 | return HttpResponseRedirect('/watcher/artifacts') 98 | else: 99 | createArtifact = CreateArtifactForm() 100 | return render(request, 'watcher/create_artifact.html', {'createArtifact': createArtifact}) 101 | 102 | @login_required 103 | def delete_artifact(request, *args, **kwargs): 104 | if request.method == 'GET': 105 | a_id = kwargs['artifact_id'] 106 | delete_artifact_helper(a_id) 107 | return HttpResponseRedirect('/watcher/artifacts') 108 | 109 | @login_required 110 | def search(request): 111 | if not 'current_project' in request.COOKIES or request.COOKIES['current_project'] == 0: 112 | return HttpResponseRedirect('/watcher/') 113 | hash_form = SearchHashForm() 114 | url_form = SearchUrlForm() 115 | return render(request, 'watcher/search_form.html', {'hash_form': hash_form, 'url_form': url_form}) 116 | 117 | @login_required 118 | def search_hash(request): 119 | if not 'current_project' in request.COOKIES or request.COOKIES['current_project'] == 0: 120 | return HttpResponseRedirect('/watcher/') 121 | if request.method == 'POST': 122 | form = SearchHashForm(request.POST) 123 | if form.is_valid(): 124 | hash_query = form.cleaned_data['hash_query'] 125 | d_search = search_artifact_helper( 126 | hash_query, request.COOKIES['current_project']) 127 | context = { 128 | 'hash_query': hash_query, 129 | 'artefact_vt_info': d_search['artefact_vt_info'], 130 | 'artefact_hybrid_info': d_search['artefact_hybrid_info'], 131 | 'artefact_otx_info': d_search['artefact_otx_info'] 132 | } 133 | return render(request, 'watcher/hash_info.html', context) 134 | else: 135 | return HttpResponseRedirect('/watcher/search') 136 | 137 | @login_required 138 | def search_artifact(request, *args, **kwargs): 139 | artifact_id = request.POST.get('artifact_id', None) 140 | search_project_artifact_helper(artifact_id) 141 | return HttpResponseRedirect('/watcher/artifacts') 142 | 143 | @login_required 144 | def search_all_artifacts(request): 145 | if not 'current_project' in request.COOKIES or request.COOKIES['current_project'] == 0: 146 | return HttpResponseRedirect('/watcher/') 147 | search_enabled_artifacts_helper(request.COOKIES['current_project']) 148 | return HttpResponseRedirect('/watcher/artifacts') 149 | 150 | 151 | # urls 152 | @login_required 153 | def get_urls(request): 154 | if not 'current_project' in request.COOKIES: 155 | c_project = 0 156 | else: 157 | c_project = request.COOKIES['current_project'] 158 | try: 159 | url_l = get_urls_helper(c_project) 160 | context = {'url_l': url_l} 161 | return render(request, 'watcher/url_list.html', context) 162 | except Exception as e: 163 | log_error.error('get_urls <' + str(e) + '>') 164 | return HttpResponseRedirect('/watcher/') 165 | 166 | @login_required 167 | def create_url(request, *args, **kwargs): 168 | if not 'current_project' in request.COOKIES or request.COOKIES['current_project'] == 0: 169 | return HttpResponseRedirect('/watcher/') 170 | if request.method == 'POST': 171 | form = CreateUrlForm(request.POST) 172 | if form.is_valid(): 173 | url_data = { 174 | 'name': form.cleaned_data['name'], 175 | 'url_query': form.cleaned_data['url_query'], 176 | 'c_project': request.COOKIES['current_project'] 177 | } 178 | create_url_helper(url_data) 179 | return HttpResponseRedirect('/watcher/urls') 180 | else: 181 | createUrl = CreateUrlForm() 182 | return render(request, 'watcher/create_url.html', {'createUrl': createUrl}) 183 | 184 | @login_required 185 | def delete_url(request, *args, **kwargs): 186 | if request.method == 'GET': 187 | u_id = kwargs['url_id'] 188 | delete_url_helper(u_id) 189 | return HttpResponseRedirect('/watcher/urls') 190 | 191 | @login_required 192 | def search_url(request, *args, **kwargs): 193 | url_id = request.POST.get('url_id', None) 194 | search_project_url_helper(url_id) 195 | return HttpResponseRedirect('/watcher/urls') 196 | 197 | @login_required 198 | def search_all_urls(request): 199 | if not 'current_project' in request.COOKIES or request.COOKIES['current_project'] == 0: 200 | return HttpResponseRedirect('/watcher/') 201 | search_enabled_url_helper(request.COOKIES['current_project']) 202 | return HttpResponseRedirect('/watcher/urls') 203 | --------------------------------------------------------------------------------