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 (join.so, libstaply.so and 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: punchcard 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 FLGBASE32A and FLGBASE32B were parts of base32-encoded flag. A glance at the function run_help from libstaply.so in IDA reveiled that service compiles the code with cobc (GnuCOBOL) and runs it. stap-ida 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:

  1. POST /upload-image.htm Upload an image of punch card, producing a string. The list of these string is stored in session.
  2. POST /list-data.htm Join selected strings from session with \n, producing a multi-line string (which is stored in session instead of it’s parts).
  3. POST /run-help.htm Run 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. 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 encoding param that service accepted along with punch card image - we always used the default 1401 IBM).
  • 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: IDENTIFICATION DIVISION. 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.

Patch

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.

Full exploit

Exploit script which uses this punchcard generator.