This is a RCE-as-a-service that runs COBOL code encoded in punch card images upladed by user. Hard parts are generating valid punch card images with desired code and writing COBOL that runs shellcode using available charset. Patch is
chmod -r data (disable listing in service data dir as flags are in randomly-named files).
Examining the service
punchymclochface (or "punchy") is a web service. First glance at service files shows it's based on Flask. When browsing service's pages we saw it allows uploading some sort of images.
A closer look to the source code revealed that a lot of it's logic is actually implemented in 3
.so libs (
decode.so) that were loaded with
ctypes.CDLL. And images uploaded to service should be punch cards. We extracted some of the images that checksystem sent to the service, they looked like this:
We also examined files of service running on our vuln and saw that several files with random-looking names appeared in
data dir, with content looking like this:
IDENTIFICATION DIVISION. PROGRAM-ID. FLAG. DATA DIVISION. WORKING-STORAGE SECTION. 01 FLG. 05 FLGBASE32A PIC X(32) VALUE "IZAVKU2UL5ME63DWOBEVSQSTJNCFETLX". 05 FLGBASE32B PIC X(32) VALUE "IFAUCQKEIREDK5CEIJHDSSCSKZGEC===";. PROCEDURE DIVISION. BEGIN. DISPLAY FLG. STOP RUN.
This is code in COBOL programming language and, as the name implies, values in
FLGBASE32B were parts of base32-encoded flag.
A glance at the function
libstaply.so in IDA reveiled that service compiles the code with
cobc (GnuCOBOL) and runs it.
We suspected that this COBOL code was somehow encoded in sent punch cards and, indeed: we replayed images sent by the checksystem and it could handle them without error, producing lines of COBOL code, one line per image.
All in all, service functionality that seemed useful for us was:
POST /upload-image.htmUpload an image of punch card, producing a string. The list of these string is stored in session.
POST /list-data.htmJoin selected strings from session with
\n, producing a multi-line string (which is stored in session instead of it's parts).
POST /run-help.htmRun selected string from session as COBOL code and get it's output.
So this is an RCE-as-a-service that runs uploaded COBOL code encoded in punch cards. If we could generate valid punch cards that the service will correctly decode to our desired text, we would be able to line-by-line upload and then run arbitrary code!
Generating punch card images
The first idea was to look at the code that parses punch cards in service. Firstly, it is preprocessed by a lot of code using
cv2 (OpenCV), this happens in
parser.py. This results in a list of 80 numbers (one number per card column) which is then passed to a function from
decode.so appeared to be
1.1M binary compiled from Go. So we thought we probably won't look at the code that parses punch cards in service.
Instead, to generate valid punchcard we used this script. (can be found by simply googling "python generate punchcard"). It produced images looking very similar to those we saw from checksystem. Few issues we faced with it were:
- While latin letters, spaces, dots and some other special chars were encoded correctly, others were not. We needed quotes for our exploit, but when we directly used it in input to the script it got replaced with other character. At the end we just tried to encode and send to the service all characters and found one that was "translated" to a quote - it appeared to be
>(probably it could also be fixed by playing with different values of
encodingparam that service accepted along with punch card image - we always used the default
- Only uppercase letters were available.
Writing exploit in COBOL
One does not simply write exploit in COBOL. Looking for 'cobol read all files from directory' in google gave us some 60-lines program in COBOL which cannot been compiled without errors. So we had to read the documentation. We were able to find suitable crunch called "SYSTEM" in section "8. Interfacing With The OS".
It allows us to make
CALL "SYSTEM" USING command
where "command" is arbitrary shell command.
Finally we got the following exploit:
IDENTIFICATION DIVISION. PROGRAM-ID. HELLO. PROCEDURE DIVISION. CALL "SYSTEM" USING "grep -a -i flgbase data/*". STOP RUN.
Making expoit works again
To test our uploaded code worked we firstly tried simple "Hello World" programs like this one.
IDENTIFICATION DIVISION. PROGRAM-ID. HELLO. PROCEDURE DIVISION. DISPLAY "HELLO WORLD". STOP RUN.
And for a long time we were unable to make them work. We compared the code to the one uploaded by checksystem and it appeared that lines should be left-padded with 7 spaces. Also, the whole line obviously can't be longer than 80 chars - it just won't fit in 80-column punch card.
Final exploit code we used to steal flags was:
PROGRAM-ID. HELLO. PROCEDURE DIVISION. BEGIN. CALL >SYSTEM> USING FUNCTION LOWER-CASE(>GREP -A -I FLGBASE DATA/*>). STOP RUN.
We used COBOL's function
LOWER-CASE to get lowercase strings,
> here will actually become quotes.
The patch is
chmod -r data - disable listing in dir with flags.
Files with flags had randomly-looking names, also, service was running under a separate user and rights on service files were rather restrictive - source files, binaries and with dirs belonged to
root, so service was unable to
chmod +x the dir back or modify itself.