# Hacked Account
#### attempted by Petiole - e-seng
## figuring things out
- first opening the file, noticed a few things:
- provided "IBAN" value: `GB29NWBK60161331926825`
- found within the input, idk if this is important
- line 174 seems to check if a value is `GB29NWBK60161331926825`, so probably important
- looks to be written in a language i'm unfamilar with... Rust possibly?
- the use of `panic(...)` and `fmt.Printf(...)` indicate Rust to me
- the use of `func` deters that...
- `code_snippets` can still be copied to `processFile.rs` to get some nice nice syntax highlighting
- could still be pseudocode of some sort. either way, doesn't matter.
- was later told this was "Go", helps to look up documentation
- ~~no main function provided, but `-B` and `-o` are cli arguments, file is named `enscript` probably~~
- ~~`ps2pdf` is another linux command~~
- `enscript -B -o | ps2pdf - legitimate.pdf` generates a pdf
- this pdf, containing an invoice ID, IBAN value and Service, is signed and then validated by the website
### reminders for myself:
- declarations take the form of one of the following
- `var <variable name> <type>`
- `<variable name> <type>`, for function arguments particularly
- `func <function name>(arguments) (return types) {...}`
- existing functions are:
- `func processFile(w http.ResponseWriter, r *http.Request){...}`
- `func checkBankAccount(path string, issigned bool) (string, error){...}`
## GOAL
- create a signed pdf file with IBAN "broken"
## code tracing
### processFile
- this is called when an unsigned pdf is uploaded most likely
- calls `checkBankAccount(...)` twice
- once at the very beginning after creating a tmp file
- if signing file:
- at this point, the user is looking to sign the pdf
- states the file is unsigned at this point
- tmp file may be prone to path redirection, using
```rs=
filename := filepath.Base(handler.Filename) + ".content.check";
```
- regardless, it is read and removed immediately
- once the file has been verified:
1. create a md5 hash of the pdf
2. convert text hash into 16 bytes of data numerical data (?)
3. creates a RSA signature based off these 16 bytes
- [notes on rsa-signatures](https://cryptobook.nakov.com/digital-signatures/rsa-signatures)
- after receiving the signature, *append* the signature to the original message
- then, write tmp file, send it back to user, delete tmp file
- if validating file:
- expects file to be at least 256 characters long
- signature is 256 characters, which are the last 256 characters of the file
- extracts the original message (bytes between the range [0, length - 256))
- hashes it, gets the numeric 16 byte value from it
- verifies the RSA signature by passing the 16 byte hash and signature
- if verfied, check if "broken" (only) is IBAN value and return flag if so.
### checkBankAccount
- opens the provided file path (to a tmp file w/ the pdf data it seems)
- is told if the file is signed
- always moves to the second row, probably to read the IBAN value?
- if the file is not signed:
- check the second row to see if the file matches a regex pattern:
- `^GB\d{2}\s?([0-9a-zA-Z]{4}\s?){4}[0-9a-zA-Z]{2}$`
- returns that the pdf is "verified" with no errors if the account number is whitelisted
## potential attack vectors
- validate pdf, alter it, then resubmit
- "money transfers occur on re-uploaded, validated forms"
- since the pdf is downloaded, try tracing the "validated" pdf for the IBAN in question and replace it
- this would get thwarted by any hashes, but we'll see i suppose
- it's a pdf... so it's iky to parse through
- after exploration of the code, it does use a hash, so this is not viable.
- try to reverse private key...?
- know that the last 256 bytes compose the rsa signature, so pull that out...?
- also, we have the original message file, and its hash
- (see signature extraction below)
- current issues with approach:
- unknown values: d, e, and n
- known values: m, s
- will be difficult to reverse probably
- mess around with values...?
- trick web application to sign the incorrect file?
- web app directly checks for the correct IBAN value
- regex may be vulnerable, does not look like it.
- (probably not going to work) try copying old RSA key, append it to target pdf file
- yeah, no lol
- mess around with carrage returns?
- since the program checks for "GB29NWBK60151331926825" and "broken" on the second line,
use a carrage return to dump both onto the second line by writing specific bytes
- (see carrage return below)
- `checkBankAccount` checks the banking number across multiple pages it seems
- validates if "GB29NWBK60161331926825" is on any third line of a page
- what happens if we create multiple pages...?
- see multipage below
### signature extraction
- create a python script that can strip the signature of the signed pdf
- expected: last 256 bytes of the file are the hash, the bytes up to the signature should contain the same md5 hash as the unsigned pdf file
```python=
#!/usr/bin/python3
import hashlib
import sys
def main():
# usage: ./extract_signature.py <signed pdf>
if len(sys.argv) < 2:
print(f"usage: {sys.argv[0]} <signed pdf file>")
return
with open(sys.argv[1], "rb") as signed_pdf_file:
read_dump = signed_pdf_file.read()
signature_bytes = read_dump[-256:-1]
message_bytes = read_dump[0:-256]
msg_hash = hashlib.md5(message_bytes)
print(f"message hash:\n{msg_hash.hexdigest()}")
print(f"signature hex:\n{signature_bytes.hex()}")
return
if __name__ == "__main__":
main()
```
- a sanity check:
```
$ md5sum legit.pdf
0f8e8a384de9531bda886191a00c705a legit.pdf
$ ./extract_signature.py legit.pdf.signed
message hash:
0f8e8a384de9531bda886191a00c705a
signature hex:
96114effc6d8886b19644f5e7f1806210d9912759f06eaa7e2a5e036616f9546c6fd69bb07eb0f6dddc77334f48e53e615150c73b417404ea1dd06f56e36d2492a23e8927f46e5acac0dde13892fe1eb1ccef10894091293271bb8389842a0df809698d4c4c2932385517a3b30e5c2bf54e87d60493af4d589014d446bf412d2457dd6e902daf6992bcfcb3e10524a3be5594b1586f357741b2b89be73560e883475851e283afdc5a999bff500dd42e5b1fd3bf436d2f79e9a896c75466364c374aa5ab4666c5ae919b2e8951f6e386baa1a4676bdf65f3bbb1e566b7fb5cddf19371d76e4dfb40ed3fe9018c933d80eeb7b7d87455cc6d956f7a6747636c3
```
### carrage return
```python=
with open("./dump.txt", "wb") as f: f.write(b"a\na\nGB29NWBK60151331926825\rbroken\n")
```
- this creates a text file with the correct bank account, but then goes back to write "broken" on the same line.
- looking at the hex-dump:
```
$ xxd dump.txt
00000000: 610a 610a 4742 3239 4e57 424b 3630 3135 a.a.GB29NWBK6015
00000010: 3133 3331 3932 3638 3235 0d62 726f 6b65 1331926825.broke
00000020: 6e n
```
- looks good i think...?
- converting this into a pdf shows
```
a
a
GB29NWBK60151331926825
broken
```
- but "broken" seems to overlap "GB29NWBK60151331926825"!!
- time to verify... here's hoping
- ... seems to have failed unfortunately...
- maybe reverse carrage return...?
```python
with open("./dump.txt", "wb") as f: f.write(b"a\na\nbroken\rGB29NWBK60151331926825\n")
```
- this also fails to sign
### multipage
- immediate question: how many lines does a page have?
- to find out: throw a lot of lines into a pdf to create 2 pages, see where one page ends
```
$ python3 -c "[print(i) for i in range(100)]" | enscript -B -o - | ps2pdf - dumb_test2.pdf
[ 2 pages * 1 copy ] left in -
```
- based off this, it is seen that a page ends at line 64
- now: let's make sure that this is still signable:
- for sanity: create pdf with 2 pages, correct bank account on first, "broken" on second
```
$ python3 -c "print('a\na\nGB29NWBK60161331926825\n'+'\n'*62+'a\na\nbroken\n')" | enscript -B -o - | ps2pdf - dumb_test2.pdf
[ 2 pages * 1 copy ] left in -
```
- here: correct bank account is on third line, first page, "broken" is on third line second page
- this signs!
- now: check if it still signs if the correct bank account is on the second page rather than the first.
```
$ python3 -c "print('a\na\nbroken\n'+'\n'*62+'a\na\nGB29NWBK60161331926825\n')" | enscript -B -o - | ps2pdf - dumb_test2.pdf
[ 2 pages * 1 copy ] left in -
```
- here: "broken" is on third line, first page, correct account id is on third line, second page.
- **this SIGNED**
- yay
- now, when i validate this, it should read "broken" first
- ***THIS WORKS!!!*** - flag got
flag: `d4451b8f-5ac2-4c0e-8f68-df0c989bf8fc`