Web Backend
Checking the backend of the target web application after performing a basic system enumeration
PS C:\Web> ls
Directory: C:\Web
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2/25/2024 4:53 PM DB Backups
d----- 2/12/2024 4:54 PM nginx-1.24.0
d----- 10/27/2024 1:36 AM University
The C:\Web
directory is the dedicated directory for the target web application
There are 3 sub-directories;
C:\Web\DB Backups
for backup operationC:\Web\nginx-1.24.0
for the web server & proxy configurationC:\Web\University
is the web application itself
nginx
PS C:\Web> cd nginx-1.24.0 ; ls
Directory: C:\Web\nginx-1.24.0
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2/12/2024 5:47 PM conf
d----- 2/12/2024 3:46 PM contrib
d----- 2/12/2024 3:46 PM docs
d----- 2/12/2024 3:46 PM html
d----- 2/17/2024 3:06 AM logs
d----- 2/12/2024 4:45 PM temp
-a---- 4/11/2023 8:29 AM 3811328 nginx.exe
-a---- 3/3/2024 4:10 AM 111067 off
-a---- 2/15/2024 12:36 AM 46 start.bat
Checking the nginx directory. There is the configuration directory
PS C:\Web\nginx-1.24.0> cd conf ; ls
Directory: C:\Web\nginx-1.24.0\conf
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/11/2023 8:31 AM 1103 fastcgi.conf
-a---- 4/11/2023 8:31 AM 1032 fastcgi_params
-a---- 4/11/2023 8:31 AM 2946 koi-utf
-a---- 4/11/2023 8:31 AM 2326 koi-win
-a---- 4/11/2023 8:31 AM 5448 mime.types
-a---- 10/18/2024 2:53 PM 1501 nginx.conf
-a---- 4/11/2023 8:31 AM 2773 nginx.conf.bak
-a---- 4/11/2023 8:31 AM 653 scgi_params
-a---- 4/11/2023 8:31 AM 681 uwsgi_params
-a---- 4/11/2023 8:31 AM 3736 win-utf
nginx.conf
Nginx Configuration
PS C:\Web\nginx-1.24.0\conf> cat nginx.conf
#user nobody;
worker_processes 1;
error_log off;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
access_log off;
log_not_found off;
sendfile on;
keepalive_timeout 65;
server_names_hash_bucket_size 64;
server {
listen 0.0.0.0:80; # Listen on HTTP port 80
server_name "^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$";
location / {
return 301 http://university.htb$request_uri;
}
}
server {
listen 0.0.0.0:80;
server_name university.htb;
client_max_body_size 3M;
location = /favicon.ico {
alias "C:\Web\University\static\assets\images\favicon.ico";
access_log off;
log_not_found off;
}
location /static/ {
root "C:\Web\University";
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:8000;
}
}
}
proxy_pass http://127.0.0.1:8000;
Web Application
PS C:\Web> cd University ; ls
Directory: C:\Web\University
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2/15/2024 8:13 AM CA
d----- 10/27/2024 12:32 AM static
d----- 10/15/2024 11:42 AM University
-a---- 10/27/2024 1:36 AM 5419 CW8nkQ.html
-a---- 10/27/2024 1:36 AM 0 CW8nkQ.pdf
-a---- 10/27/2024 1:36 AM 245760 db.sqlite3
-a---- 12/3/2023 4:28 AM 666 manage.py
-a---- 2/15/2024 12:51 AM 133 start-server.bat
Web application directory
start-server.bat
PS C:\Web\University> cat start-server.bat
cat start-server.bat
@ECHO OFF
cd C:\Web\University
"C:\Program Files\Python310\python.exe" -u .\manage.py runserver --noreload --no-color --skip-checks
The batch file is likely run via a scheduled task
manage.py
PS C:\Web\University> cat manage.py
cat manage.py
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'University.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
The manage.py
file for management
It loads University.settings
db.sqlite3
PS C:\Web\University> copy db.sqlite3 .\static\db.sqlite3
┌──(kali㉿kali)-[~/archive/htb/labs/university]
└─$ curl -s http://university.htb/static/db.sqlite3 -o ./db.sqlite3
I will also transfer the DB file
┌──(kali㉿kali)-[~/archive/htb/labs/university]
└─$ file db.sqlite3
db.sqlite3: SQLite 3.x database, last written using SQLite version 3037002, file counter 596, database pages 60, 1st free page 60, free pages 3, cookie 0x5b, schema 4, UTF-8, version-valid-for 596
┌──(kali㉿kali)-[~/archive/htb/labs/university]
└─$ open db.sqlite3
Opening it up
There are a total of 16 tables
University_customuser
Table
The
University_customuser
table contains user data
I will grab all the password hashes.
The hash format appears to be pbkdf2 from werkzeug.security . This requires reformatting in order to crack them.
University_professor
Table
The
University_professor
table shows PGP public key endpoints for professor users
django_migrations
Table
The
django_migrations
table appears to shows migration logs
CA
PS C:\Web\University> cd CA ; ls
Directory: C:\Web\University\CA
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2/15/2024 5:51 AM 1399 rootCA.crt
-a---- 2/15/2024 5:48 AM 1704 rootCA.key
-a---- 2/25/2024 5:41 PM 42 rootCA.srl
PS C:\Web\University\CA> cat rootCA.crt
-----BEGIN CERTIFICATE-----
MIID2zCCAsOgAwIBAgIULPFQaeEBSoZuCoVP9n/EvYSynY4wDQYJKoZIhvcNAQEL
BQAwfTELMAkGA1UEBhMCVUsxEzARBgNVBAgMClNvbWUtU3RhdGUxFzAVBgNVBAoM
DlVuaXZlcnNpdHkgTHRkMRcwFQYDVQQDDA51bml2ZXJzaXR5Lmh0YjEnMCUGCSqG
SIb3DQEJARYYaGVhZGFkbWluQHVuaXZlcnNpdHkuaHRiMB4XDTI0MDIxNTEzNTEx
N1oXDTI5MDIxMzEzNTExN1owfTELMAkGA1UEBhMCVUsxEzARBgNVBAgMClNvbWUt
U3RhdGUxFzAVBgNVBAoMDlVuaXZlcnNpdHkgTHRkMRcwFQYDVQQDDA51bml2ZXJz
aXR5Lmh0YjEnMCUGCSqGSIb3DQEJARYYaGVhZGFkbWluQHVuaXZlcnNpdHkuaHRi
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnd50RP0SO7Xkz5Yztt1z
cW1nc1oFx5lWYDrZCDXSIUvDOrdCVC5nGdIJIduKHrKeBhVSkUTR1DKNte7RjX9s
RlPra4qFV/0CmB5eBr8ddD6rF8LCYP2tIEHTXlXhYA64x++MIIq5CaGxiTW5dogN
9qnHXo3Po3zizE0KLF3DQGlPNqZZCjIw0+i8zGPjFRMOBkdZafFmnvs3L6NQ3Uk4
J6osqZ+RuEJ6uAIlU9urF8B7NDUHaLIqwBXPWJCzscietDjHq5bslXGX+6pr+iBy
eYbk9YvHYgdNzEljZtU8gT5k0gOeC9xbRdUP9+jjh8e0nYfRy6WdSPIGvpD8NuhU
+QIDAQABo1MwUTAdBgNVHQ4EFgQUwA3KIy0t91zWLrghiiotr5q4X4YwHwYDVR0j
BBgwFoAUwA3KIy0t91zWLrghiiotr5q4X4YwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAQEAgrA9JK4pl4x8gvvx3y6FRZUUK+fj1lqDXhkFHsdOf42u
SI+iC+e/VC+FfUX2A5IB7ObyEKa9x7KnjTL2dQOkxUV40ElecaUFI3tKn/ZmhbK7
RnqIBvLH7uWfy7rrefiMMW9UIYP9EWkXEwKzDPT1BDT8QDpGjMODFmFarehOT6hZ
43WgsXxwg0Smbmph56SDBWUe/gdNKVFk5J8g+qBfU+Ci7++si6WFPTQ/ZfEIoGpv
svRSfBOivVo4un/basHbzUwpnGO+qm5u2eYxiCCXZye17O8BaqagqNYPGzftKfKE
688xD+PVJjj0H80YhA6n0qaL+oO7kUJwCfmBerlH2w==
-----END CERTIFICATE-----
PS C:\Web\University\CA> cat rootCA.key
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCd3nRE/RI7teTP
ljO23XNxbWdzWgXHmVZgOtkINdIhS8M6t0JULmcZ0gkh24oesp4GFVKRRNHUMo21
7tGNf2xGU+trioVX/QKYHl4Gvx10PqsXwsJg/a0gQdNeVeFgDrjH74wgirkJobGJ
Nbl2iA32qcdejc+jfOLMTQosXcNAaU82plkKMjDT6LzMY+MVEw4GR1lp8Wae+zcv
o1DdSTgnqiypn5G4Qnq4AiVT26sXwHs0NQdosirAFc9YkLOxyJ60OMerluyVcZf7
qmv6IHJ5huT1i8diB03MSWNm1TyBPmTSA54L3FtF1Q/36OOHx7Sdh9HLpZ1I8ga+
kPw26FT5AgMBAAECggEACD+yRtQL3rmYfvXH1jlgQbggGI2GwYkMM7tDcZvBUurO
5MukUDp7+eQJBN9AKm42FlaJIczNI3ewQqY7xhdXQJ38Ljp20PQIDYuBqXvVNcib
/0H2juOWZxn1aceWPwMJ6e2E/oLNHBkaKhOX8R73DtFMeO7zP2VEQv1QB6UvEXeS
ILuMKzcIoGOeBOjxOiUeInhTpgdyVCtFDun2TkHgw0GCmnXPwzpu8k1vsKCnkO4N
dEcJMrwwxdWcp1DwPsDlylKy5p8goY9feT5KIxkxWxvHEvQiRK8CE34hR3PmemtD
FCXQAMkP0NQxOyufA8VMhtxnEGkAlhU1KDmX1AiPQQKBgQDZYBaszCxmC8ET+S2u
w+rgNz8EA+JHWVJ4zDvXTfl6ksG7X7N/cgqYg0PfYspHj++rVyXd8ni/yz7qkAsv
v02H6wnGGNGjUWxpKXoKBrqRTMFZTCmavXwCWm552PkXZvRAQfI3/t73ALIygE2V
I9yd3/U2mIHreDVkLthB0ZOmQQKBgQC5644enw/xaz2GFEN5GOwgkelKbekxaTfN
88nqOP+xFkjc8VcPy4G9sA5nukNcB5sbXP/NDL+zmc1MKRfGcuxhuUn6I5M53sT0
KiGzmrqTHTsNPzrkHOmCCW47IxxNTHjF/bfiFf5KYkXIMw4ckA7pwvOB/gJrCoMl
j7sdy8kwuQKBgES6ZdDyqSMGmmBnS3NTTILh1NG/LJ8kdbft0wnLBJMlrY1Bv+4O
r5zy+9W4VX502vFQl49qJ5V0fIsLSg4prSjJPojctgOuLdm+HHDpGbnxC7zhINJl
EL1pvD5YRMGKcrWSZDQiKHruRGlFOcJq2UktU0mUxJcfpbnsOcHtsoJBAoGAWt4t
DPxTD3yJimGXBLXQmq6V5gHIKpUxaCCR/D81gTjSDeKK2bZuR+anc70OtQkmnv4a
K/7iJwax11pyO1TqAW1NIcuHZo5hlrCM3jpum/56YSQZRWp4Gk0/qfmkGIeas7Io
0IBNU8+qKYoCiKVEFK5q04rxZZ2ysYYJPyvdvkkCgYAjg8ihJk0tywHrsWReOdHt
5P0hJp6NsjsRwHk6raWTbwEs7eaafVKkWVLB7T5jCshA3nU5/5Wqr5uD5MY/Ctou
jIA32+TSyU0DV57626o2Pis7cIzmcUY2JKmtFUlvPrOROQp/ZxcW3Sm9Ef3hVyGz
JIMTfCnxScVEVWH3AyhAZw==
-----END PRIVATE KEY-----
PS C:\Web\University\CA> cat rootCA.srl
6A6B3B653FBA7D80BE108F9824FA1F2EBF502340
The CA directory contains a rootCA keypair. It was likely used to signed the user certificate for authentication in the target web application. It would be rather questionable if this keypair is used system/domain-wise
static
PS C:\Web\University> tree /F /A .\static
tree /F /A .\static
Folder PATH listing
Volume serial number is 000002AF 8E7E:469B
C:\WEB\UNIVERSITY\STATIC
| db.sqlite3
|
+---admin
| +---css
| | | autocomplete.css
| | | base.css
| | | changelists.css
| | | dark_mode.css
| | | dashboard.css
| | | forms.css
| | | login.css
| | | nav_sidebar.css
| | | responsive.css
| | | responsive_rtl.css
| | | rtl.css
| | | widgets.css
| | |
| | \---vendor
| | \---select2
| | LICENSE-SELECT2.md
| | select2.css
| | select2.min.css
| |
| +---img
| | | calendar-icons.svg
| | | icon-addlink.svg
| | | icon-alert.svg
| | | icon-calendar.svg
| | | icon-changelink.svg
| | | icon-clock.svg
| | | icon-deletelink.svg
| | | icon-no.svg
| | | icon-unknown-alt.svg
| | | icon-unknown.svg
| | | icon-viewlink.svg
| | | icon-yes.svg
| | | inline-delete.svg
| | | LICENSE
| | | README.txt
| | | search.svg
| | | selector-icons.svg
| | | sorting-icons.svg
| | | tooltag-add.svg
| | | tooltag-arrowright.svg
| | |
| | \---gis
| | move_vertex_off.svg
| | move_vertex_on.svg
| |
| \---js
| | actions.js
| | autocomplete.js
| | calendar.js
| | cancel.js
| | change_form.js
| | collapse.js
| | core.js
| | filters.js
| | inlines.js
| | jquery.init.js
| | nav_sidebar.js
| | popup_response.js
| | prepopulate.js
| | prepopulate_init.js
| | SelectBox.js
| | SelectFilter2.js
| | theme.js
| | urlify.js
| |
| +---admin
| | DateTimeShortcuts.js
| | RelatedObjectLookups.js
| |
| \---vendor
| +---jquery
| | jquery.js
| | jquery.min.js
| | LICENSE.txt
| |
| +---select2
| | | LICENSE.md
| | | select2.full.js
| | | select2.full.min.js
| | |
| | \---i18n
| | af.js
| | ar.js
| | az.js
| | bg.js
| | bn.js
| | bs.js
| | ca.js
| | cs.js
| | da.js
| | de.js
| | dsb.js
| | el.js
| | en.js
| | es.js
| | et.js
| | eu.js
| | fa.js
| | fi.js
| | fr.js
| | gl.js
| | he.js
| | hi.js
| | hr.js
| | hsb.js
| | hu.js
| | hy.js
| | id.js
| | is.js
| | it.js
| | ja.js
| | ka.js
| | km.js
| | ko.js
| | lt.js
| | lv.js
| | mk.js
| | ms.js
| | nb.js
| | ne.js
| | nl.js
| | pl.js
| | ps.js
| | pt-BR.js
| | pt.js
| | ro.js
| | ru.js
| | sk.js
| | sl.js
| | sq.js
| | sr-Cyrl.js
| | sr.js
| | sv.js
| | th.js
| | tk.js
| | tr.js
| | uk.js
| | vi.js
| | zh-CN.js
| | zh-TW.js
| |
| \---xregexp
| LICENSE.txt
| xregexp.js
| xregexp.min.js
|
\---assets
+---css
| animate.min.css
| aos.css
| bootstrap-icons.css
| bootstrap.min.css
| dashboard.css
| dashboard.css.map
| dashboard.scss
| dropzone.min.css
| fancybox.min.css
| flaticon.css
| footer.css
| footer.css.map
| footer.scss
| magnific-popup.min.css
| meanmenu.css
| metismenu.min.css
| navbar.css
| navbar.css.map
| navbar.scss
| odometer.min.css
| owl.carousel.min.css
| owl.theme.default.min.css
| remixicon.css
| responsive.css
| responsive.css.map
| responsive.scss
| selectize.min.css
| simplebar.min.css
| style.css
| style.css.map
| style.scss
| templatemo-topic-listing.css
|
+---fonts
| bootstrap-icons.woff
| bootstrap-icons.woff2
| flaticon.eot
| flaticon.svg
| flaticon.ttf
| flaticon.woff
| flaticon.woff2
| remixicon.eot
| remixicon.glyph.json
| remixicon.less
| remixicon.ttf
| remixicon.woff
| remixicon.woff2
|
+---images
| | businesswoman-using-tablet-analysis.jpg
| | colleagues-working-cozy-office-medium-shot.jpg
| | faq_graphic.jpg
| | favicon.ico
| | logo.png
| | rear-view-young-college-student.jpg
| |
| +---topics
| | colleagues-working-cozy-office-Med-shot.png
| | undraw_Compose_music_re_wpiw.png
| | undraw_Educator_re_ju47.png
| | undraw_Finance_re_gnv2.png
| | undraw_Graduation_re_gthn.png
| | undraw_Group_video_re_btu7.png
| | undraw_happy_music_g6wc.png
| | undraw_online_ad_re_ol62.png
| | undraw_Podcast_audience_re_4i5q.png
| | undraw_Redesign_feedback_re_jvm0.png
| | undraw_Remote_design_team_re_urdx.png
| | undraw_viral_tweet_gndb.png
| |
| \---users_profiles
| 2.png
| 3.jpg
| 4.jpg
| 5.jpg
| 6.jpg
| 7.jpeg
| 7.png
| default.png
|
+---js
| aos.js
| bootstrap.bundle.min.js
| ckeditor.js
| click-scroll.js
| contact-form-script.js
| crypto-js.min.js
| custom.js
| dropzone.min.js
| fancybox.min.js
| form-validator.min.js
| jquery.ajaxchimp.min.js
| jquery.appear.js
| jquery.magnific-popup.min.js
| jquery.meanmenu.js
| jquery.min.js
| jquery.sticky.js
| main.js
| metismenu.min.js
| odometer.min.js
| owl.carousel.min.js
| selectize.min.js
| simplebar.min.js
| sticky-sidebar.min.js
| TweenMax.min.js
| wow.min.js
|
\---uploads
+---CSRs
| 5.csr
| 7.csr
|
+---lectures
| Final.pdf
| Final_MlrkzDx.pdf
| Introduction.zip
| Introduction_1OnecYs.pdf
| Introduction_5EI2V6N.pdf
| Introduction_AlhP5SY.zip
| Introduction_C9gTnh7.zip
| Introduction_js8tUqk.zip
| Introduction_K03KQx6.pdf
| Introduction_kxkxQaD.pdf
| Introduction_NipeQ7F.pdf
| Introduction_RcgJmpV.pdf
| Introduction_SSoPdei.pdf
| Introduction_WmlgnJn.pdf
| Lecture_1.zip
| Lecture_1_1XT4vz8.pdf
| Lecture_1_8nIvW6r.pdf
| Lecture_1_ApxrD10.pdf
| Lecture_1_asYfCuk.pdf
| Lecture_1_H9WWf40.pdf
| Lecture_1_ODEhS5y.pdf
| Lecture_1_R3eXrJg.pdf
| Lecture_1_TzqNKFS.zip
| Lecture_1_Uknmc7Z.pdf
| Lecture_2.zip
| Lecture_2_0jZM7aA.pdf
| Lecture_2_ajXyOBo.zip
| Lecture_2_J98vE7l.pdf
| Lecture_2_jt0HAUm.pdf
| Lecture_2_K0rTKRT.pdf
| Lecture_2_qXVpr8T.pdf
| Lecture_2_TAZsMpP.pdf
| Lecture_2_W11aioL.zip
| Lecture_2_ZMkKb0U.pdf
| Lecture_3.zip
| Lecture_3_1HpStAM.pdf
| Lecture_3_6hM13vA.pdf
| Lecture_3_8y7kpIA.pdf
| Lecture_3_E8zbku8.zip
| Lecture_3_iAm6zMY.zip
| Lecture_3_LWfCylC.pdf
| Lecture_3_PafSEok.pdf
| Lecture_3_pmNY6qw.pdf
| Lecture_3_SNtLsz4.zip
| Lecture_3_vCwFo21.zip
| Lecture_4.zip
| Lecture_4_eGdPzAb.pdf
| Lecture_4_ExbjtY6.zip
| Lecture_4_viQILAn.pdf
| Lecture_4_xkuXOUH.pdf
| Lecture_4_y5yHLtk.pdf
| Lecture_5.pdf
| Lecture_5_3lBylH3.pdf
| Lecture_5_Zx0Jdq2.pdf
| Lecture_6.pdf
| Lecture_6_90sbuCq.pdf
| Lecture_7.pdf
| Lecture_8.pdf
| Perfect-Lecture-Sample.zip
|
+---Pub_KEYs
| 2.asc
| 3.asc
| 4.asc
| 5.asc
| 6.asc
|
\---signatures
Final.pdf.sig
Final.pdf_lGkgLN4.sig
Introduction.pdf.sig
Introduction.pdf_3CYSit9.sig
Introduction.pdf_aevENk4.sig
Introduction.pdf_BNRkFQy.sig
Introduction.pdf_EHaX0e9.sig
Introduction.pdf_ez3Ck0N.sig
Introduction.pdf_j9HQjl2.sig
Introduction.pdf_TYTT8n0.sig
Introduction.pdf_XzFZSxS.sig
Lecture_1.pdf.sig
Lecture_1.pdf_6wTlZJN.sig
Lecture_1.pdf_DD61DLp.sig
Lecture_1.pdf_E0a2iwg.sig
Lecture_1.pdf_LyssY9q.sig
Lecture_1.pdf_ManFIsc.sig
Lecture_1.pdf_mbgAUjA.sig
Lecture_1.pdf_OqCzSTp.sig
Lecture_1.pdf_saJ5iNv.sig
Lecture_1.pdf_Ze4KwYO.sig
Lecture_2.pdf.sig
Lecture_2.pdf_29XUEuJ.sig
Lecture_2.pdf_atodAJt.sig
Lecture_2.pdf_NesVcdk.sig
Lecture_2.pdf_oTq05Ek.sig
Lecture_2.pdf_OwYdvsr.sig
Lecture_2.pdf_PNsmeWv.sig
Lecture_2.pdf_pVjK5Wx.sig
Lecture_2.pdf_RoDMsiO.sig
Lecture_2.pdf_VFp6jKV.sig
Lecture_3.pdf.sig
Lecture_3.pdf_3CVey0P.sig
Lecture_3.pdf_cCAJWAe.sig
Lecture_3.pdf_lxG83A6.sig
Lecture_3.pdf_RkkrZBy.sig
Lecture_3.pdf_SoYK3qM.sig
Lecture_3.pdf_toX8vUb.sig
Lecture_3.zip.sig
Lecture_4.pdf.sig
Lecture_4.pdf_A5GL2rQ.sig
Lecture_4.pdf_jP93tcb.sig
Lecture_4.pdf_lGiR3ZL.sig
Lecture_4.pdf_otlIj84.sig
Lecture_5.pdf.sig
Lecture_5.pdf_eqrAnLi.sig
Lecture_5.pdf_z3y05cg.sig
Lecture_6.pdf.sig
Lecture_6.pdf_XQpqHon.sig
Lecture_7.pdf.sig
Lecture_8.pdf.sig
Perfect-Lecture-Sample.zip.sig
There are 2 CSR files in the static/assets/uploads/CSRs
directory;
5.csr
7.csr
The assets\uploads\Pub_KEYs
directory contains Public PGP key blocks;
2.asc
3.asc
4.asc
5.asc
6.asc
5.csr
*Evil-WinRM* PS C:\Web\University\static\assets\uploads\CSRs> cat 5.csr
-----BEGIN CERTIFICATE REQUEST-----
MIICyTCCAbECAQAwgYMxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRl
MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFDASBgNVBAMMC21h
cnRpbi5yb3NlMSYwJAYJKoZIhvcNAQkBFhdtYXJ0aW4ucm9zZUBob3RtYWlsLmNv
bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANogIhiJao2d/3oEOzRS
7523b4Aat8EsnYzVhGpbtARGPmLYJYIZaBmqhAUt3tFIkWwGa0wyGFodH6E2gPqh
2NO6E9wRsPFSR0BcBj3KoanMVzLr34K6/mAOgNRxCgMAiCB1LiZF7nSPoax3HifL
690sB9cRwqzMSsF0yuYX1/1uPdg/WrB+dfDgGIvzbFaTUVXvgNHGhBq4W5lOQUlB
tLf8UgD9Hagu9uZw8Nd7MobqmFvZ7qWmVtKwBecNTzHAUu+t30rZawHp29DGT6jC
fT0heaIjWDyPhQWA/kbNQw6KuafyeWP3cfxAr97GYswiWBlv0NaDa4E073IxJdh6
llUCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQAURkomwJlWrbzOoZZi2bshV/x2
gOWrO499UBslwuBjYihy3e75KKjgMzLXA+q+9BKgUPpS3J88tWLha/Xu427PgHFG
vxV9irgwvzaY0IwdouAu7Ok6czbmWilM4cyrsgty1V/d6LUU4D03y6Z2QATfPKA+
epAy70Tmg8BKVOcYJMDyPjxutu2chMqPxISDKRIAHnG2MFb37oxsUrzs1KGDR609
V9450uplSyDukY6HjwzNA+Hy81E06PZimGQzRcmBJ79TwwEWd8fhiKe94VDbkrJm
Ou5n75i426bj58sgfcMTGTmWNGXDvpOehGeCus26SebC+jyy1LteZT02oWk3
-----END CERTIFICATE REQUEST-----
┌──(kali㉿kali)-[~/…/htb/labs/university/certs]
└─$ openssl req -in 5.csr -text -noout | grep -w 'Subject:'
Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=martin.rose, emailAddress=martin.rose@hotmail.com
The 5.csr
file belongs to the martin rose
7.csr
*Evil-WinRM* PS C:\Web\University\static\assets\uploads\CSRs> cat 7.csr
-----BEGIN CERTIFICATE REQUEST-----
MIICvjCCAaYCAQAweTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UEAwwIU3Rl
dmVuLlUxHzAdBgkqhkiG9w0BCQEWEHN0ZXZlbkB5YWhvby5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3k0uVdocW8NetEK1r0upXF69HsbYPwUM6
0WfPS4Xx/00udkNlEJqpTsOI/nIoO3haq3P5G35T2m1hg959BGE30C3jkCL3znjc
VIPOe8jvsqJ43n52iIIbD6za66jyzhXezFpBpWJVfOu1tpk9Ybe9ZOJAuSsY7Zpa
DipcPgdVVfIQz/pwZMJhmjL4YdFhPvHOpoE0mHwkqZwnWjOwJa9b1hoGbEcnCyoo
8G3UZNIOG+J97YJ4jMQpFLB5A4x+IbOfRboR18cLRUSZBm1r9Scz4ZM81WEV8sl+
OFAQd6AV2SPCCHCsPwDjebw07bOlrQlrDbX0G0gybPxtKTztvUSlAgMBAAGgADAN
BgkqhkiG9w0BAQsFAAOCAQEAp3QlBtHiekDgns+UZjRnDhuFjDsug94dfwz7Somw
4Pqu4VPSMwKhb+5NhkbFSnZnZouIOkU+DThL6LcvFGYz6ymGB7u+ueKZtuYzaCkt
Ysvpd27DzcYcrHzVIKEGOJCLN4TX5fn56i+8U0t/1jjNx3mAFCxFaDphGxwbt8bB
G7qgu5IxWMiAbYAkYXz4VpqlLQSRxieZpOYDEN7ww8SsTxPNET1/kQQI/2I3PQsq
w7INxKIMIC+RcbfIH0lWqWiXN8QbX80BR4dFb5Eysi+C6YXBiYX8DOssMV8SuS6x
vhfFC+DaGw2JWmloFzhUIA7RINVtf1Oe303fkjeHYRRx5g==
-----END CERTIFICATE REQUEST-----
┌──(kali㉿kali)-[~/…/htb/labs/university/certs]
└─$ openssl req -in 7.csr -text -noout | grep -w 'Subject:'
Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Steven.U, emailAddress=steven@yahoo.com
The 7.csr
file belongs to Steven.U
University
PS C:\Web\University> cd University ; ls
Directory: C:\Web\University\University
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 3/4/2024 4:09 PM migrations
d----- 3/13/2024 11:17 AM templates
d----- 3/9/2024 11:39 PM __pycache__
-a---- 2/7/2024 12:39 AM 237 admin.py
-a---- 12/3/2023 4:28 AM 397 asgi.py
-a---- 2/15/2024 9:08 AM 2364 certificate_utils.py
-a---- 10/19/2023 8:10 PM 436 customMiddlewares.py
-a---- 2/15/2024 9:42 AM 3278 custom_decorators.py
-a---- 2/15/2024 12:48 PM 8296 forms.py
-a---- 3/4/2024 4:07 PM 3979 models.py
-a---- 2/25/2024 4:56 PM 3479 settings.py
-a---- 3/4/2024 8:27 PM 1893 urls.py
-a---- 10/15/2024 11:27 AM 23410 views.py
-a---- 12/3/2023 4:28 AM 397 wsgi.py
-a---- 12/3/2023 4:28 AM 0 __init__.py
This appears to be the backend directory of the target web application
settings.py
Evil-WinRM* PS C:\Web\University\University> cat settings.py
"""
Django settings for University project.
Generated by 'django-admin startproject' using Django 4.2.7.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-fs-2bin)f_nd1q5jly9_g8$9e$2y2_zy!pn=*qji^i*-v5yt7#'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = ['university.htb']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'University',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'University.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'University.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'University.CustomUser'
rooCA_Cert = os.path.join(BASE_DIR, 'CA/rootCA.crt')
rooCA_PrivKey = os.path.join(BASE_DIR, 'CA/rootCA.key')
The settings.py
file appears to set environment variables
views.py
*Evil-WinRM* PS C:\Web\University\University> cat views.py
from .certificate_utils import *
from .custom_decorators import course_owner_required, professor_required, PSC_required, PubKeyring_required, student_required
from .forms import *
from .models import *
from base64 import urlsafe_b64encode
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import authenticate, login
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.views import LogoutView
from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
from django.http import HttpResponseRedirect, HttpResponseBadRequest
from django.shortcuts import render, redirect, get_object_or_404
from django.template.loader import get_template
from django.urls import reverse, reverse_lazy
from django.utils.encoding import force_bytes
from django.conf import settings
import random
import string
import subprocess
def index(request):
return render(request, 'index.html')
def contact(request):
if request.method == "POST":
if ("name" in request.POST) and ("email" in request.POST) and ("title" in request.POST) and ("message" in request.POST):
messages.success(request,"we have received your message, our team will review it and contact you as soon as possible")
else:
messages.error(request, "an error has occured... please check your submission and try again.")
context = {"title": "Contact Us"}
return render(request, 'contact.html', context)
def page_not_found(request, exception):
context = {"title": "Page Not Found"}
return render(request, 'error-404.html', context,status=404)
class MyLogoutView(LogoutView):
next_page = reverse_lazy('auth_login')
def get_next_url(self):
return self.next_page
def Student_register(request):
if request.method == 'POST':
form = StudentCreationForm(request.POST)
if form.is_valid():
# process the form data
user = form.save(commit=False)
# set the user_type field to Student
user.user_type = "Student"
# save the user
user.save()
return redirect(reverse('auth_login'))
else:
# form is not valid
context = {"title": "Student Register", "form": form}
else:
form = StudentCreationForm()
context = {"title": "Student Register", "form": form}
return render(request, 'register.html', context)
def Professor_register(request):
if request.method == 'POST':
form = ProfessorCreationForm(request.POST)
if form.is_valid():
# process the form data
user = form.save(commit=False)
# set the user_type field to Freelancer
user.user_type = "Professor"
# Professor account should be activated be the admin after the review
user.is_active = False
# save the user
user.save()
return redirect(reverse('auth_login'))
else:
# form is not valid
context = {"title": "Professor Register", "form": form}
else:
messages.success(request, 'Note: After creating your "Professor" account, your account will be inactive until our team reviews your account details and contacts you by email to activate your account.')
form = ProfessorCreationForm()
context = {"title": "Professor Register", "form": form}
return render(request, 'register.html', context)
def auth_login(request):
if request.method == 'POST':
# check if account is deactivated
if CustomUser.objects.filter(username=request.POST["username"]):
user = CustomUser.objects.get(username=request.POST["username"])
if not user.is_active:
form = AuthenticationForm()
messages.error(request, "Sorry, this account is not activated and can not be authenticated!.")
return render(request, 'login.html', {'form': form, 'title': 'Login'})
# check creds
form = AuthenticationForm(data=request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
user.reset_failed_login_attempts()
if 'next' in request.GET:
return HttpResponseRedirect(request.GET["next"])
return redirect(reverse('profile'))
else:
# if the user account exists --> increase the "failed_login_attempts"
try:
user = CustomUser.objects.get(
username=request.POST["username"])
user.increment_failed_login_attempts()
if user.failed_login_attempts == 10:
messages.error(request, "Due to multiple bad login attempts, we have deactivated your account...")
messages.error(request, "To re-activate your account you have to provide the valid security answers for your account.")
return redirect(reverse('account_recovery'))
except ObjectDoesNotExist:
pass
else:
form = AuthenticationForm()
return render(request, 'login.html', {'form': form, 'title': 'Login'})
def auth_SDC_login(request):
if request.method == 'POST':
if 'SDC' not in request.FILES or request.FILES["SDC"].size == 0:
return HttpResponseBadRequest("ERROR: Malicious behavior has been detected!")
form = SDC_Login_Form(request.POST, request.FILES)
if form.is_valid():
SDC = form.cleaned_data.get('SDC')
SDC_data = SDC.read()
# try to verify the certificate
if not verify_signed_Cert(SDC_data):
return HttpResponseBadRequest("ERROR: Invalid signed certificate file!")
username ,email = get_certificate_details(SDC_data)
user = CustomUser.objects.filter(username = username, email=email)
if user.exists():
login(request, user.get())
return redirect(reverse('profile'))
else:
return HttpResponseBadRequest('Invalid user primary key!')
else:
errors = form.errors.as_data()
for field, error_list in errors.items():
for error in error_list:
messages.error(request, error.message)
else:
form = SDC_Login_Form()
context = {
'form':form,
'title': 'Signed Digital Certificate Login',
}
return render(request, 'sdc_login.html', context)
@login_required
def profile(request):
if request.method == 'POST':
# determine the user type
if request.user.user_type == "Student":
user = Student.objects.get(id=request.user.id)
form = StudentProfileForm(request.POST, instance=user)
elif request.user.user_type == "Professor":
user = Professor.objects.get(id=request.user.id)
form = ProfessorProfileForm(request.POST, instance=user)
if form.is_valid():
form.save()
messages.success(request, 'Your profile has been updated successfully.')
# returning to the function will render the profile page again with the updates accquired
return redirect('profile')
else:
if request.user.user_type == "Student":
user = Student.objects.get(id=request.user.id)
form = StudentProfileForm(instance=user)
elif request.user.user_type == "Professor":
user = Professor.objects.get(id=request.user.id)
form = ProfessorProfileForm(instance=user)
context = {
'form': form,
'title': 'My Profile',
'user': user,
}
return render(request, 'profile.html', context)
@login_required
def pdf_profile(request):
if request.user.user_type == "Student":
user = Student.objects.get(id=request.user.id)
form = StudentProfileForm(instance=user)
#get student enrolled in courses
try:
courses = user.courses.all()
except:
courses = []
elif request.user.user_type == "Professor":
user = Professor.objects.get(id=request.user.id)
form = ProfessorProfileForm(instance=user)
#get professor created courses
try:
courses = Course.objects.filter(teacher=user)
except:
courses = []
else:
return HttpResponseBadRequest("ERROR: Malicious behavior has been detected!")
context = {
'form': form,
'user': user,
'courses':courses,
}
template = get_template('pdf_profile.html')
html = template.render(context)
random_prefix = ''.join(random.choices(string.ascii_letters + string.digits, k=6))
with open(f"{random_prefix}.html", "w") as html_file:
html_file.write(html)
try:
subprocess.run(["C:\\Program Files\\Python310\\Scripts\\xhtml2pdf.exe", f"{random_prefix}.html"], shell=False, check=False)
except subprocess.CalledProcessError as e:
print(f"Command execution failed: {e.output}")
return None
pdf_file = open(f"{random_prefix}.pdf", 'rb').read()
response = HttpResponse(pdf_file, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="profile.pdf"'
# get rid of the temp files
os.remove(f"{random_prefix}.html")
os.remove(f"{random_prefix}.pdf")
return response
def visit_profile(request, user_id):
if not user_id.isdigit():
return HttpResponseBadRequest("WARNING: Malicious input has been detected!")
custom_user = get_object_or_404(CustomUser, id=user_id)
if custom_user.user_type == "Student":
user = Student.objects.get(id=custom_user.id)
form = StudentProfileForm(instance=user)
#get student enrolled in courses
try:
courses = user.courses.all()
except:
courses = []
elif custom_user.user_type == "Professor":
user = Professor.objects.get(id=custom_user.id)
form = ProfessorProfileForm(instance=user)
#get professor created courses
try:
courses = Course.objects.filter(teacher=user)
except:
courses = []
else:
return HttpResponseBadRequest("ERROR: Malicious behavior has been detected!")
context = {
'form': form,
'title': f'{user.username} Profile',
'user': user,
'courses':courses,
}
return render(request, 'visit_profile.html', context)
@login_required
def upload_profile_image(request):
if request.method == 'POST':
user = CustomUser.objects.get(id=request.user.id)
if 'image' in request.FILES:
form = ProfileImageForm(request.POST, request.FILES, instance=user)
if form.is_valid():
if request.user.image and 'default.png' not in request.user.image.path:
old_image_path = request.user.image.path
os.remove(old_image_path)
form.save()
messages.success(request, 'Profile image uploaded successfully.')
else:
# display form with error messages
errors = form.errors.as_data()
for field, error_list in errors.items():
for error in error_list:
messages.error(request, error.message)
else:
return HttpResponseBadRequest("WARNING: Malicious behavior has been detected!")
else:
return HttpResponseBadRequest("WARNING: Malicious behavior has been detected!")
return redirect(reverse('profile'))
@login_required
@professor_required
def upload_public_key(request):
if request.method == 'POST':
user = Professor.objects.get(id=request.user.id)
if user.public_key:
old_public_key_path = user.public_key.path
form = Professor_PubKEY_Form(request.POST, request.FILES, instance=user)
if form.is_valid():
if user.public_key:
try:
os.remove(old_public_key_path)
except:
pass
form.save()
messages.success(request, 'Public Key uploaded successfully.')
else:
# display form with error messages
errors = form.errors.as_data()
for _, error_list in errors.items():
for error in error_list:
messages.error(request, error.message)
else:
form = Professor_PubKEY_Form()
context = {
'title': 'GPG key upload',
'form' : form
}
return render(request, 'upload-gpg-pubKey.html', context)
@login_required
def course(request):
if "page_number" in request.GET and request.GET["page_number"].isdigit() and int(request.GET["page_number"]) > 0:
page_number = request.GET["page_number"]
else:
page_number=1
# intially get all the courses
courses = Course.objects.order_by('id')
# Create a Paginator object
paginator = Paginator(courses, 7)
# Get the desired page
courses = paginator.get_page(page_number)
context = {'title': 'Course dashboard', 'courses': courses, 'page_number':page_number, 'user': request.user}
return render(request, 'course-dashboard.html', context)
@login_required
@professor_required
def my_courses(request):
professor = Professor.objects.get(id=request.user.id)
# intially get all the courses
courses = Course.objects.filter(teacher=professor)
context = {'title': 'My Courses', 'courses': courses,'page_number': 1, 'user': professor}
return render(request, 'course-dashboard.html', context)
@login_required
@PSC_required
def create_course(request):
form = CourseForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
course = form.save(commit=False)
course.teacher = Professor.objects.get(customuser_ptr_id=request.user.id)
course.save()
messages.success(request, 'New Course created successfully!')
return redirect(reverse('course_details', kwargs={'course_id':course.id}))
else:
# display form with error messages
errors = form.errors.as_data()
for field, error_list in errors.items():
for error in error_list:
messages.error(request, error.message)
context = {'form': form, 'title': 'Create Course', 'user': request.user}
return render(request, 'create_course.html', context)
@login_required
@PSC_required
@course_owner_required
def edit_course(request, course_id):
if not course_id.isdigit():
return HttpResponseBadRequest("WARNING: Malicious input has been detected!")
course = get_object_or_404(Course, id=course_id)
if request.method == 'POST':
form = CourseForm(request.POST, instance=course)
if form.is_valid():
form.save()
messages.success(request, 'Your course details has been updated successfully.')
return redirect(reverse('course_details',kwargs={'course_id':course_id}))
else:
form = CourseForm(instance=course)
context = {
'form': form,
'title': 'Edit course details',
}
return render(request, 'create_course.html', context)
@login_required
@student_required
def course_enroll(request, course_id):
if not course_id.isdigit():
return HttpResponseBadRequest("WARNING: Malicious input has been detected!")
course = get_object_or_404(Course, id=course_id)
student = Student.objects.get(id=request.user.id)
if student not in course.students.all():
course.students.add(student)
student.courses.add(course)
return redirect(reverse('course_details',kwargs={'course_id':course_id}))
@login_required
def course_details(request, course_id):
if course_id.isdigit():
course = get_object_or_404(Course, id=course_id)
# assume the Student is not enrolled initially
Enrolled = False
Owner = False
lectures = []
# let's assume he is Professor in the begginning
if request.user.user_type == "Student":
student = Student.objects.get(id=request.user.id)
# Check if the Student has already enrolled
if student in course.students.all():
Enrolled = True
lectures = Lecture.objects.filter(course=course, is_verified=True)
elif request.user.user_type == "Professor":
if course.teacher.id == request.user.id:
Owner = True
context = {
"title": course.title + " - Course Details",
"course": course,
"lectures": lectures,
"Enrolled": Enrolled,
"Owner": Owner,
"user": request.user,
}
return render(request, 'course-details.html', context)
else:
return HttpResponseBadRequest("WARNING: Malicious content has been detected!")
@login_required
@professor_required
@course_owner_required
@PubKeyring_required
def upload_lecture(request, course_id):
if request.method == 'POST':
form = LectureForm(request.POST, request.FILES)
if form.is_valid() and course_id.isdigit():
lecture = form.save(commit=False)
lecture.course = get_object_or_404(Course, id=course_id)
file_name = request.FILES['file'].name
file_path = os.path.join(settings.STATIC_ROOT, 'assets', 'uploads', 'lectures', file_name)
if os.path.exists(file_path):
messages.error(request, f'Error: A file named "{file_name}" already exists.')
context = {'form': form, 'title': 'Upload a new Lecture', 'user': request.user}
return render(request, 'upload_lecture.html', context)
else:
lecture.save()
# get the professor to read his pubkey
professor = Professor.objects.get(id=request.user.id)
verification = form.verify_integrity(lecture.Digital_Sign.path, lecture.file.path, professor.public_key.path)
if not verification.valid:
lecture.delete()
os.remove(lecture.Digital_Sign.path)
os.remove(lecture.file.path)
messages.error(request, '<ul class="errorlist"><li>Error: Invalid Lecture Integrity!. Please make sure that:</li><li>Your public key is a valid gpg-public-key.</li><li>The deteached signature file is created by the same user who owns the public key.</li></ul>')
else:
messages.success(request, 'The lecture is uploaded successfully, our team will review it and contact you soon...')
return redirect(reverse('upload_lecture', kwargs={'course_id':course_id}))
else:
# display form with error messages
errors = form.errors.as_data()
for field, error_list in errors.items():
for error in error_list:
messages.error(request, error.message)
else:
form = LectureForm()
context = {'form': form, 'title': 'Upload a new Lecture', 'user': request.user}
return render(request, 'upload_lecture.html', context)
'''
@login_required
@professor_required
@PubKeyring_required
def upload_signed_file(request):
if request.method == 'POST':
if 'file' in request.FILES:
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
obj = form.save(commit=False)
user = Professor.objects.get(id=request.user.id)
obj.professor = user
obj.save()
verification = form.verify_integrity(obj.DigiSign.path, obj.file.path, user.public_key.path)
if not verification.valid:
obj.delete()
os.remove(obj.DigiSign.path)
os.remove(obj.file.path)
messages.error(request, 'Error: Invalid File Integrity!')
else:
obj.date = datetime.fromtimestamp(int(verification.timestamp))
obj.save()
messages.success(request, 'Signed File uploaded successfully.')
else:
# display form with error messages
errors = form.errors.as_data()
for field, error_list in errors.items():
for error in error_list:
messages.error(request, error.message)
return(redirect('upload_signed_file'))
else:
return HttpResponseBadRequest("WARNING: Malicious behavior has been detected!")
else:
form = DocumentForm()
context = {
'form': form,
'title': 'Upload Signed File',
}
return render(request, 'upload.html', context)
'''
@login_required
def request_signed_cert(request):
if request.method == 'POST':
if 'csr' in request.FILES:
form = CSR_form(request.POST, request.FILES, instance=request.user)
if form.is_valid():
if request.user.csr:
try:
os.remove(request.user.csr.path)
except:
pass
form.save()
# check if the CSR info matches professor information
try:
common_name, email_address = get_csr_details(request.user.csr.path)
except:
return HttpResponseBadRequest("WARNING: Malicious behavior has been detected!")
if common_name == request.user.username and email_address == request.user.email:
return generate_signed_cert(request, request.user)
else:
return HttpResponseBadRequest("ERROR: CSR information dose not match your database records!")
else:
# display form with error messages
errors = form.errors.as_data()
for field, error_list in errors.items():
for error in error_list:
messages.error(request, error.message)
else:
return HttpResponseBadRequest("WARNING: Malicious behavior has been detected!")
else:
form = CSR_form()
context = {
'form': form,
'title': 'Request a signed certificate',
}
return render(request, 'request-signed-certificate.html', context)
return redirect(reverse('request_signed_cert'))
def get_lectures_list(request):
lectures = Lecture.objects.filter(is_verified=False, Evaluating=False)
lectures_list = ''
for lecture in lectures:
lectures_list += lecture.file.path.split("\\")[-1] + '\n'
lecture.Evaluating = True
lecture.save()
return HttpResponse(lectures_list)
Checking the views.py
file reveals the inner working of the web application
certificate_utils.py
*Evil-WinRM* PS C:\Web\University\University> cat certificate_utils.py
from .settings import rooCA_Cert, rooCA_PrivKey
from base64 import b64encode
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from django.http import HttpResponse, HttpResponseBadRequest
import subprocess
def get_csr_details(csr_path):
with open(csr_path, "rb") as csr_file:
csr = x509.load_pem_x509_csr(csr_file.read(), default_backend())
subject = csr.subject
common_name = subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)[0].value
email_address = subject.get_attributes_for_oid(x509.NameOID.EMAIL_ADDRESS)[0].value
return common_name, email_address
def verify_signed_Cert(signed_cert_bytes):
with open(rooCA_Cert, "rb") as ca_file:
ca_certificate = x509.load_pem_x509_certificate(ca_file.read(), default_backend())
try:
signed_certificate = x509.load_pem_x509_certificate(signed_cert_bytes, default_backend())
ca_certificate.public_key().verify(
signed_certificate.signature,
signed_certificate.tbs_certificate_bytes,
padding.PKCS1v15(),
signed_certificate.signature_hash_algorithm,
)
return True
except:
return False
def get_certificate_details(cert_bytes):
cert = x509.load_pem_x509_certificate(cert_bytes, default_backend())
subject = cert.subject
common_name = subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)[0].value
email_address = subject.get_attributes_for_oid(x509.NameOID.EMAIL_ADDRESS)[0].value
return common_name, email_address
def generate_signed_cert(request, user):
command = r'"C:\\Program Files\\openssl-3.0\\x64\\bin\\openssl.exe" x509 -req -in "{}" -CA "{}" -CAkey "{}" -CAcreateserial'.format(user.csr.path, rooCA_Cert, rooCA_PrivKey)
output = subprocess.check_output(command, shell=True).decode()
# Base64 encode the content of the file
encoded_output = b64encode(output.encode()).decode()
response = HttpResponse(content_type='application/x-x509-ca-cert')
# Set the content of the file as a cookie
response.set_cookie('PSC', encoded_output)
response['Content-Disposition'] = 'attachment; filename="signed-cert.pem"'
response.write(output)
return response
The certificate_utils.py
file is responsible for dealing with certificates including signing and issuing
It uses the following command;
"C:\\Program Files\\openssl-3.0\\x64\\bin\\openssl.exe" x509 -req -in "{}" -CA "{}" -CAkey "{}" -CAcreateserial
This may be used for certificate forgery